使用Xcode 11 beta 6,我尝试使用@Published
为具有属性的类型声明一个协议(但我想这个问题可以推广到任何PropertyWrapper)。
final class DefaultWelcomeViewModel: WelcomeViewModel & ObservableObject {
@Published var hasAgreedToTermsAndConditions = false
}
为此,我试图声明:
protocol WelcomeViewModel {
@Published var hasAgreedToTermsAndConditions: Bool { get }
}
这会导致编译错误:Property 'hasAgreedToTermsAndConditions' declared inside a protocol cannot have a wrapper
所以我尝试将其更改为:
protocol WelcomeViewModel {
var hasAgreedToTermsAndConditions: Published<Bool> { get }
}
并尝试
这不会编译,DefaultWelcomeViewModel does not conform to protocol
,好吧,嗯,那我不能用Published<Bool>
,让我们试试吧!
struct WelcomeScreen<ViewModel> where ViewModel: WelcomeViewModel & ObservableObject {
@EnvironmentObject private var viewModel: ViewModel
var body: some View {
// Compilation error: `Cannot convert value of type 'Published<Bool>' to expected argument type 'Binding<Bool>'`
Toggle(isOn: viewModel.hasAgreedToTermsAndConditions) {
Text("I agree to the terms and conditions")
}
}
}
// MARK: - ViewModel
protocol WelcomeViewModel {
var hasAgreedToTermsAndConditions: Published<Bool> { get }
}
final class DefaultWelcomeViewModel: WelcomeViewModel & ObservableObject {
var hasAgreedToTermsAndConditions = Published<Bool>(initialValue: false)
}
这会导致Toggle
上的编译错误:Cannot convert value of type 'Published<Bool>' to expected argument type 'Binding<Bool>'
。
问:如何使用 PropertyWrapper 为具体类型的属性创建协议属性?
我认为您提出的明确问题与您尝试解决的问题不同,但我会尽力帮助解决这两个问题。
首先,您已经意识到不能在协议中声明属性包装器。这是因为属性包装器声明在编译时合成为三个单独的属性,这不适用于抽象类型。
因此,要回答您的问题,您不能在协议中显式声明属性包装器,但您可以为属性包装器的每个合成属性创建单独的属性要求,例如:
protocol WelcomeViewModel {
var hasAgreed: Bool { get }
var hasAgreedPublished: Published<Bool> { get }
var hasAgreedPublisher: Published<Bool>.Publisher { get }
}
final class DefaultWelcomeViewModel: ObservableObject, WelcomeViewModel {
@Published var hasAgreed: Bool = false
var hasAgreedPublished: Published<Bool> { _hasAgreed }
var hasAgreedPublisher: Published<Bool>.Publisher { $hasAgreed }
}
如您所见,两个属性(_hasAgreed
和$hasAgreed
)已由具体类型的属性包装器合成,我们可以简单地从协议所需的计算属性中返回这些属性。
现在我相信我们的Toggle
完全有一个不同的问题,编译器很高兴地提醒我们:
无法将类型为"已发布"的值转换为预期的参数类型"绑定">
此错误也很简单。Toggle
期望一个Binding<Bool>
,但我们试图提供一个不同类型的Published<Bool>
。幸运的是,我们选择使用@EnvironmentObject
,这使我们能够使用viewModel
上的"投影值"来获取视图模型属性的Binding
。可以使用符合条件的属性包装器上的$
前缀访问这些值。事实上,我们已经在上面用hasAgreedPublisher
属性完成了此操作。
因此,让我们更新我们的Toggle
以使用Binding
:
struct WelcomeView: View {
@EnvironmentObject var viewModel: DefaultWelcomeViewModel
var body: some View {
Toggle(isOn: $viewModel.hasAgreed) {
Text("I agree to the terms and conditions")
}
}
}
通过在viewModel
前面加上$
,我们可以访问一个支持视图模型"动态成员查找"的对象,以便获得视图模型成员的Binding
。
我会考虑另一种解决方案 - 让协议定义一个包含所有状态信息的类属性:
protocol WelcomeViewModel {
var state: WelcomeState { get }
}
WelcomeState 具有@Published
属性:
class WelcomeState: ObservableObject {
@Published var hasAgreedToTermsAndConditions = false
}
现在,您仍然可以从视图模型实现中发布更改,但可以直接从视图中观察它:
struct WelcomeView: View {
@ObservedObject var welcomeState: WelcomeState
//....
}