@Published property wrapper not working on subclass of ObservableObject
Finally figured out a solution/workaround to this issue. If you remove the property wrapper from the subclass, and call the baseclass objectWillChange.send() on the variable the state is updated properly.
NOTE: Do not redeclare let objectWillChange = PassthroughSubject<Void, Never>()
on the subclass as that will again cause the state not to update properly.
I hope this is something that will be fixed in future releases as the objectWillChange.send()
is a lot of boilerplate to maintain.
Here is a fully working example:
import SwiftUI
class MyTestObject: ObservableObject {
@Published var aString: String = ""
}
class MyInheritedObject: MyTestObject {
// Using @Published doesn't work on a subclass
// @Published var anotherString: String = ""
// If you add the following to the subclass updating the state also doesn't work properly
// let objectWillChange = PassthroughSubject<Void, Never>()
// But if you update the value you want to maintain state
// of using the objectWillChange.send() method provided by the
// baseclass the state gets updated properly... Jaayy!
var anotherString: String = "" {
willSet { self.objectWillChange.send() }
}
}
struct MyTestView: View {
@ObservedObject var myTestObject = MyTestObject()
@ObservedObject var myInheritedObject = MyInheritedObject()
var body: some View {
NavigationView {
VStack(alignment: .leading) {
TextField("Update aString", text: self.$myTestObject.aString)
Text("Value of aString is: \(self.myTestObject.aString)")
TextField("Update anotherString", text: self.$myInheritedObject.anotherString)
Text("Value of anotherString is: \(self.myInheritedObject.anotherString)")
}
}
}
}
This is because ObservableObject is a protocol, so your subclass must conform to the protocol, not your parent class
Example:
class MyTestObject {
@Published var aString: String = ""
}
final class MyInheritedObject: MyTestObject, ObservableObject {
@Published var anotherString: String = ""
}
Now, @Published properties for both class and subclass will trigger view events
iOS 14.5 resolves this issue.
Combine
Resolved Issues
Using Published in a subclass of a type conforming to ObservableObject now correctly publishes changes. (71816443)
UPDATE
This has been fixed in iOS 14.5 and macOS 11.3, subclasses of ObservableObject will correctly publish changes on these versions. But note that the same app will exhibit the original issues when run by a user on any older minor OS version. You still need the workaround below for any class that is used on these versions.
The best solution to this problem that I've found is as follows:
Declare a BaseObservableObject
with an objectWillChange
publisher:
open class BaseObservableObject: ObservableObject {
public let objectWillChange = ObservableObjectPublisher()
}
Then, to trigger objectWillChange
in your subclass, you must handle changes to both observable classes and value types:
class MyState: BaseObservableObject {
var classVar = SomeObservableClass()
var typeVar: Bool = false {
willSet { objectWillChange.send() }
}
var someOtherTypeVar: String = "no observation for this"
var cancellables = Set<AnyCancellable>()
init() {
classVar.objectWillChange // manual observation necessary
.sink(receiveValue: { [weak self] _ in
self?.objectWillChange.send()
})
.store(in: &cancellables)
}
}
And then you can keep on subclassing and add observation where needed:
class SubState: MyState {
var subVar: Bool = false {
willSet { objectWillChange.send() }
}
}
You can skip inheriting BaseObservableObject
in the root parent class if that class already contains @Published
variables, as the publisher is then synthesized. But be careful, if you remove the final @Published
value from the root parent class, all the objectWillChange.send()
in the subclasses will silently stop working.
It is very unfortunate to have to go through these steps, because it is very easy to forget to add observation once you add a variable in the future. Hopefully we will get a better official fix.