2
votes

I am trying to understand the MVVM pattern in SwiftUI, but I am not understanding exactly how the ViewModel listens to and propagates changes from a Model object. Many examples, including this one from Apple, talk about having a Model inherit from ObservableObject and use that directly in your View. This makes sense.

But what is the best/recommended way that a ViewModel should observe changes to its underlying Model?

A simple example is a WidgetView that displays the title of a widget, but the title can change in the background from a background network call, for example.

class WidgetView: View {
    @ObservedObject var widgetVM = WidgetViewModel()

    var body: some View {
        Text(widgetVM.title)
    }
}

class WidgetViewModel : ObservableObject {
   var widget: Widget

   var title: String {
      get {
         // Some translation to the title for this particular view
         return widget.title + "!"
      }
   }
}

struct Widget {
    // Some other timer or background process is changing the title
    var title: String
}

One rough solution I've explored is having a separate title and listening for changes. So if Widget extended ObservableObject and @Published the title field, then the WidgetViewModel could do the following:

class WidgetViewModel : ObservableObject {
   var widget: Widget

   @Published var title: String = ""
   var cancellable: AnyCancellable?

   init() {
       self.cancellable = widget.$title.receive(on: DispatchQueue.main)
           .sink(receiveValue: self.updateTitle )
   }

   func updateTitle(_: String) {
       self.title = widget.title + "!"
   }
}

Is it recommended/standard for Widget to extend ObservableObject too? If so, how does WidgetViewModel properly pass notifications of changes through? It would seem that WidgetModelView.widget would need to be both a @Published and an @ObservedObject, but that doesn't seem right.

Anyone have any insight here?

1
You don't need widget to be @Published. Your Widget can publish changes to its title through Combine as you have shown or you could use Notification or any other method you like. You don't need Widget to conform to ObservableObject in order to have a publisher for title. You could expose a Subject for title - Paulw11
Assuming I used a different notification mechanism from the Model to the ViewModel (I will research these), is there a way to notify the View without having a Published property that I set (I.e. so that I don’t have to have a separate title field in the ViewModel)? - JaredC
No, that is the cost of having both a model and a view model. Your view model typically has a bunch of glue code to propogate events up/down. - Paulw11

1 Answers

0
votes

Your Widget is struct, so it cannot be ObservableObject, instead you can just do the following

class WidgetViewModel : ObservableObject {

   var widget: Widget {
     didSet {
        updateTitle(widget)    // << here !!
     }
   }

   @Published var title: String = ""

   func updateTitle(_: String) {
       self.title = widget.title + "!"
   }
}