【问题标题】:PropertyWrappers and protocol declaration?PropertyWrappers 和协议声明?
【发布时间】:2019-08-26 06:41:37
【问题描述】:

使用 Xcode 11 beta 6,我正在尝试为具有使用 @Published 的属性的类型声明一个协议(但这个问题可以推广到 任何 PropertyWrapper I猜)。

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&lt;Bool&gt;,那我们试试吧!

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&lt;Bool&gt;' to expected argument type 'Binding&lt;Bool&gt;'上的编译错误。

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

【问题讨论】:

  • 再次阅读我的问题:)
  • 我最接近它的是:stackoverflow.com/a/57527336/7786555
  • 这个问题可能应该被标记为该问题的重复。但问题是提供更多背景信息。太糟糕了,我们没有真正“到达那里”,所以无法编译?
  • 是的,我虽然关闭为重复但不希望您的 cmets “丢失”。也许如果您移动它们作为对另一个问题的答复,我们可以关闭这个。无论如何,我确实尝试了不同的访问类型,但也许我遗漏了一些东西。请自己试一试并发布您的结果(如果有的话;-)。干杯。
  • 这里正在讨论解决此问题的方法:forums.swift.org/t/property-wrapper-requirements-in-protocols/…

标签: swift swiftui xcode11


【解决方案1】:

我认为你提出的明确问题与你试图解决的问题不同,但我会尽力帮助解决这两个问题。

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

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

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&lt;Bool&gt;,但我们正在尝试提供一个不同类型的 Published&lt;Bool&gt;。幸运的是,我们选择使用@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

【讨论】:

  • var hasAgreedPublished: Published&lt;Bool&gt; { get } 是做什么用的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多