Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 655 Vote(s) - 3.48 Average
  • 1
  • 2
  • 3
  • 4
  • 5
@Published property wrapper not working on subclass of ObservableObject

#1
I have a class conforming to the @ObservableObject protocol and created a subclass from it with it's own variable with the @Published property wrapper to manage state.

It seems that the @published property wrapper is ignored when using a subclass. Does anyone know if this is expected behaviour and if there is a workaround?

I'm running iOS 13 Beta 8 and xCode Beta 6.

Here is an example of what I'm seeing. When updating the TextField on ```MyTestObject``` the Text view is properly updated with the aString value. If I update the ```MyInheritedObject```TextField the anotherString value isn't updated in the Text view.

```swift
import SwiftUI

class MyTestObject: ObservableObject {
@Published var aString: String = ""

}

class MyInheritedObject: MyTestObject {
@Published var anotherString: String = ""
}

struct TestObserverWithSheet: View {
@ObservedObject var myTestObject = MyInheritedObject()
@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)")
}
}
}
}
```
Reply

#2
**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.
Reply

#3
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)
Reply

#4
From my experience, just chain the subclass `objectWillChange` with the base class's `objectWillChange` like this:
```
class GenericViewModel: ObservableObject {

}

class ViewModel: GenericViewModel {
@Published var ...
private var cancellableSet = Set<AnyCancellable>()

override init() {
super.init()

objectWillChange
.sink { super.objectWillChange.send() }
.store(in: &cancellableSet)
}
}
```
Reply

#5
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
Reply

#6
This happens also when your class is not directly subclass `ObservableObject`:

class YourModel: NSObject, ObservableObject {

@Published var value = false {
willSet {
self.objectWillChange.send()
}
}
}
Reply

#7
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:
```swift
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)")
}
}
}
}
```
Reply



Forum Jump:


Users browsing this thread:
2 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through