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