Change Notification from Observable Object as a Nested Object in Swiftui

Change Notification from Observable Object as a Nested Object in SwiftUI

Changes aren't detected because Foo, being a reference-type, doesn't actually change - it's the same reference, so @Published doesn't help here.

AppState would need to manually subscribe to changes and call its own objectWillChange.send:

class AppState: ObservableObject {

var foo: Foo = Foo() {
didSet {
cancellables = []
foo.$counter
.map { _ in } // ignore actual values
.sink(receiveValue: self.objectWillChange.send)
.store(in: &cancellables)
}
}

private var cancellables: Set<AnyCancellable> = []
}

How to tell SwiftUI views to bind to nested ObservableObjects

Nested models does not work yet in SwiftUI, but you could do something like this

class SubModel: ObservableObject {
@Published var count = 0
}

class AppModel: ObservableObject {
@Published var submodel: SubModel = SubModel()

var anyCancellable: AnyCancellable? = nil

init() {
anyCancellable = submodel.objectWillChange.sink { [weak self] (_) in
self?.objectWillChange.send()
}
}
}

Basically your AppModel catches the event from SubModel and send it further to the View.

Edit:

If you do not need SubModel to be class, then you could try something like this either:

struct SubModel{
var count = 0
}

class AppModel: ObservableObject {
@Published var submodel: SubModel = SubModel()
}

How to post a notification when an observable object has changed

If I correctly understood your intention the solution is to subscribe to default publisher which posts event on any change, i.e.

class MyClass: ObservableObject {

@Published var date: Date = Date() // default values just for demo simplicity
@Published var a: Int = 0
@Published var b: Int = 0

private var subscriber: AnyCancellable!
init() {
subscriber = self.objectWillChange.sink { [weak self] in
guard let object = self else { return }
NotificationCenter.default.post(name: .modified, object: object)
}
}
}

SwiftUI Combine: Nested Observed-Objects

When a Ball in the balls array changes, you can call objectWillChange.send() to update the ObservableObject.

The follow should work for you:

class BallManager: ObservableObject {
@Published var balls: [Ball] {
didSet { setCancellables() }
}
let ballPublisher = PassthroughSubject<Ball, Never>()
private var cancellables = [AnyCancellable]()

init() {
self.balls = []
}

private func setCancellables() {
cancellables = balls.map { ball in
ball.objectWillChange.sink { [weak self] in
guard let self = self else { return }
self.objectWillChange.send()
self.ballPublisher.send(ball)
}
}
}
}

And get changes with:

.onReceive(bm.ballPublisher) { ball in
print("ball update:", ball.id, ball.color)
}

Note: If the initial value of balls was passed in and not always an empty array, you should also call setCancellables() in the init.

SwiftUI - changes in nested View Model classes not detected using onChange method

Figure it out. Just had to create another AnyCancellable variable to call objectWillChange publisher.

WatchDayProgramViewModel

class WatchDayProgramViewModel: ObservableObject {

@Published var workoutModel = WorkoutModel()
var cancellable: AnyCancellable?

init() {
cancellable = workoutModel.objectWillChange
.sink { _ in
self.objectWillChange.send()
}
}

}

While I have provided my answer, that worksaround with viewmodels, I would love to see/get advice on other alternatives.

Updates changes for nested models

In your current code, menu is only ever set once (on init). You want to set menu every time the object updates.

To do this, change the menu after the settings object has changed one of the values. Here we receive on the main thread, then update the menu with setMenu().

You could also change the UserSettings to use didSet instead of willSet, and then you will no longer require the .receive(on: DispatchQueue.main). However, this may be a bit counter-intuitive to the meaning of objectWillChange.

Code:

class ViewModel: ObservableObject {
let settings = UserSettings()
@Published private(set) var menu: [Item] = []
private var cancellables: [AnyCancellable] = []

init() {
setMenu()

settings.objectWillChange.receive(on: DispatchQueue.main).sink { [unowned self] in
setMenu()
}
.store(in: &cancellables)
}

private func setMenu() {
menu = [
.item(Language(id: "a", enable: settings.itemA)),
.item(Language(id: "b", enable: settings.itemB)),
.item(Language(id: "c", enable: settings.itemC))
]
}
}

I did change some things slightly which would be unrelated, such as the access levels. You don't want the user to accidentally edit menu directly.



Related Topics



Leave a reply



Submit