PropertyWrappers and protocol declaration?

后端 未结 1 1177
野性不改
野性不改 2020-12-29 11:42

Using Xcode 11 beta 6, I am trying to declare a protocol for a type with properties using @Published (but this question can be generalized to <

相关标签:
1条回答
  • 2020-12-29 12:08

    I think the explicit question you're asking is different from the problem you are trying to solve, but I'll try to help with both.

    First, you've already realized you cannot declare a property wrapper inside a protocol. This is because property wrapper declarations get synthesized into three separate properties at compile-time, and this would not be appropriate for an abstract type.

    So to answer your question, you cannot explicitly declare a property wrapper inside of a protocol, but you can create individual property requirements for each of the synthesized properties of a property wrapper, for example:

    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 }
    }
    

    As you can see, two properties (_hasAgreed and $hasAgreed) have been synthesized by the property wrapper on the concrete type, and we can simply return these from computed properties required by our protocol.

    Now I believe we have a different problem entirely with our Toggle which the compiler is happily alerting us to:

    Cannot convert value of type 'Published' to expected argument type 'Binding'

    This error is straightforward as well. Toggle expects a Binding<Bool>, but we are trying to provide a Published<Bool> which is not the same type. Fortunately, we have chosen to use an @EnvironmentObject, and this enables us to use the "projected value" on our viewModel to obtain a Binding to a property of the view model. These values are accessed using the $ prefix on an eligible property wrapper. Indeed, we have already done this above with the hasAgreedPublisher property.

    So let's update our Toggle to use a Binding:

    struct WelcomeView: View {
        @EnvironmentObject var viewModel: DefaultWelcomeViewModel
    
        var body: some View {
            Toggle(isOn: $viewModel.hasAgreed) {
                Text("I agree to the terms and conditions")
            }
        }
    }
    

    By prefixing viewModel with $, we get access to an object that supports "dynamic member lookup" on our view model in order to obtain a Binding to a member of the view model.

    0 讨论(0)
提交回复
热议问题