I was naive to create a view model using a service that is also an ObservableObject
. It looks like this:
class BookViewModel: ObservableObject {
let bookService = BookService()
}
class BookService: ObservableObject {
@Published var count = 42
}
In my SwiftUI view, I use viewModel.bookService.count
, expecting that the view will bind and update when the count changes. It should work because count
is a @Published
, right?
Nope. That doesn’t work, because from the view point-of-view 👀, BookService
is a nested ObservableObject
. It will not receive any publishing.
How to publish with nested ObservableObject
The solution is for the main object (view model) to observe the nested object (services), and publish manually using objectWillChange
.
This is the fix for my BookViewModel
.
class BookViewModel {
init() {
// Propagate the change from nested ObservableObject
bookService.objectWillChange
.receive(on: RunLoop.main)
.sink { [weak self] in
self?.objectWillChange.send()
}
.store(in: &cancellables)
}
}
If you have multiple services, then use the operator Publishers.Merge(bookService.objectWillChange, anotherService.objectWillChange)
.
Perhaps one day, SwiftUI will support such a use. For now, this is a workaround.
UPDATE: Republishing Solution
@mergesort showed me how he solved it in Boutique, and adam-zethraeus packaged it as a @Republished property wrapper. Cool solution.
UPDATE 2023: Using new Observable
The new @Observable
in iOS 17+ changed everything (replacing ObservableObject
and @Published
), and supports nested observable object!