PropertyWrappers和协议声明?

时间:2019-08-26 06:41:37

标签: swift swiftui xcode11

我使用 Xcode 11 beta 6 ,尝试使用@Published为具有属性的类型声明协议(但该问题可以推广为 any 我猜是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)
}

这会导致ToggleCannot convert value of type 'Published<Bool>' to expected argument type 'Binding<Bool>'上的编译错误。

问题:如何使用PropertyWrappers为具体类型的属性创建协议属性?

1 个答案:

答案 0 :(得分:6)

我认为您要提出的明确问题与您要解决的问题不同,但我会尽力为您提供帮助。

首先,您已经意识到不能在协议内声明属性包装器。这是因为属性包装器声明在编译时被合成为三个单独的属性,而这不适用于抽象类型。

因此,要回答您的问题,您不能在协议内部明确声明属性包装器,但是可以为属性包装器的每个综合属性创建单独的属性要求,例如:

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),我们可以简单地从协议所需的计算属性中返回它们。 / p>

现在,我相信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模型。