Reputation: 684
I have recently started to dig into the wonderful world of SwiftUI, Combine, and property wrappers and am struggling to combine @ObservedObject with the @Injected property wrapper I wrote to inject dependencies into my views. Most of the time my @Injected wrapper works fine, but when paired with @ObservedObject to manage my viewmodels I received "Property type does not match that of the 'wrappedValue' property" errors."
Here's currently what my @Injected property wrapper looks like:
@propertyWrapper
public struct Injected<Service> {
private var service: Service
public init() {
self.service = assembler.resolver.resolve(Service.self)!
}
public init(name: String? = nil, container: Resolver? = nil) {
// `assembler` here referring to my global Swinject assembler
self.service = container?.resolve(Service.self, name: name) ??
assembler.resolver.resolve(Service.self, name: name)!
}
public var wrappedValue: Service {
get {
return service
}
mutating set {
service = newValue
}
}
public var projectedValue: Injected<Service> {
get {
return self
}
mutating set {
self = newValue
}
}
}
And here is my current usage:
struct MyModalView: View {
@ObservedObject @Injected var viewModel: MyModalViewModel
var body: some View {
Text("Hello World")
}
}
Ordering the wrappers in this way, I receive: "Property type 'MyModalViewModel' does not match that of the 'wrappedValue' property of its wrapper type 'ObservedObject'", while the MyModalViewModel class does extend from ObservableObject.
If I flip the wrappers around, it compiles, but Swinject tries to resolve a wrapped ObservedObject class, and because the container is just registering the original MyModalViewModel class, this resolution fails and the app crashes.
Meanwhile, assigning the @ObservedObject value through direct assignment works:
@ObservedObject var viewModel: MyModalViewModel = assembler.resolver.resolve(MyModalViewModel.self)!
I would think the original code should compile, seeing @Injected will return a wrapped value that conforms to ObservableObject, like @ObservedObject expects, though all of this is still fairly new to me so there might be something I'm missing. Any input here would be greatly appreciated. Thanks!!
Upvotes: 2
Views: 1552
Reputation: 134
Use @InjectedObject annotation, or Service locator from Resolver. And, don't forget to register your initializer.
Upvotes: 0
Reputation: 684
For anyone coming upon this in the future, I wound up combining the two property wrappers under one roof, borrowing the implementation from the Resolver project:
@propertyWrapper
public struct InjectedObject<Service>: DynamicProperty where Service: ObservableObject {
@ObservedObject private var service: Service
public init() {
self.service = assembler.resolver.resolve(Service.self)!
}
public init(name: String? = nil, container: Resolver? = nil) {
self.service = container?.resolve(Service.self, name: name) ??
assembler.resolver.resolve(Service.self, name: name)!
}
public var wrappedValue: Service {
get { return service }
mutating set { service = newValue }
}
public var projectedValue: ObservedObject<Service>.Wrapper {
return self.$service
}
}
Upvotes: 5