14

I have two classes nested in another class, which is an observable object in a SwiftUI view. Even though properties in the nested classes are declared as @Published, their values (when they change) do not update in the main view.

A similar question has been asked here, and I could use it to get it to work for one of the two subclasses, but not both.

How to tell SwiftUI views to bind to nested ObservableObjects

This is the model:

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

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

class Model: ObservableObject {
  @Published var submodel1: Submodel1 = Submodel1()
  @Published var submodel2: Submodel2 = Submodel2()
}

And this is the main view:

struct ContentView: View {
  @ObservedObject var model: Model = Model()

  var body: some View {
    VStack {
      Text("Count: \(model.submodel1.count)")
        .onTapGesture {
          self.model.submodel1.count += 1
        }
      Text("Count: \(model.submodel2.count)")
        .onTapGesture {
          self.model.submodel2.count += 1
        }
    }
  }
}

Adding this to the model class (see previous Stackoverflow question) works for updating on submodel1 changes, but not both:

  var anyCancellable: AnyCancellable? = nil
  init() {
      anyCancellable = submodel1.objectWillChange.sink { (_) in
          self.objectWillChange.send()
      }
   }

What I'm looking for is some way to pass on changes of both the submodel1 and submodel2 to my view.

mxt533
  • 225
  • 2
  • 6
  • I have justed posted an alternative solution [in this answer](https://stackoverflow.com/a/63291258/1837715). I hope that helps. – VoodooBoot Aug 06 '20 at 20:35

1 Answers1

12

You can expand upon the answer in the question you linked to by using CombineLatest to have your model fire its objectWillChange publisher when either of the underlying objects change:

import Combine

class Model: ObservableObject {
    @Published var submodel1: Submodel1 = Submodel1()
    @Published var submodel2: Submodel2 = Submodel2()

    var anyCancellable: AnyCancellable? = nil

    init() {
        anyCancellable = Publishers.CombineLatest(submodel1.$count,submodel2.$count).sink(receiveValue: {_ in
            self.objectWillChange.send()
        })
    }
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • This is a good solution, I was using the view modifier `onReceive` block with the publisher to update a state variable and then used that used that state variable in the view body. – user1046037 Dec 05 '19 at 05:47
  • combine latest seems to have a maximum of 4 objects? What if we have more than 4 objects? – Marwan Roushdy Apr 28 '20 at 05:31
  • I'd like to second Marwan's Question: what if I have more objects than Publishers.CombineLatest4 can handle? https://developer.apple.com/documentation/combine/publishers/combinelatest4 – Gin Tonyx Jan 19 '21 at 15:07
  • Now that I have more experience with combine and SwiftUI I probably wouldn't use this approach. I would create a `ViewModel` that used the `Model` to expose and handle updates to the relevant properties. The view model subscribes to the properties in the model and exposes new published properties. – Paulw11 Jan 19 '21 at 20:00
  • Just wanted to take a moment to appreciate how this answer saved my day. My last three days, actually. It was driving me mad, I could not find a workaround to the problem. Thx. – Gin Tonyx Jan 19 '21 at 22:06