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?
widget
to be@Published
. YourWidget
can publish changes to itstitle
through Combine as you have shown or you could useNotification
or any other method you like. You don't needWidget
to conform toObservableObject
in order to have a publisher fortitle
. You could expose aSubject
fortitle
- Paulw11