read

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!


Image

@samwize

¯\_(ツ)_/¯

Back to Home