I’ve just started learning swift and was going to build this number-incrementing sample app to understand MVVM. I don’t understand why is my number on the view not updating upon clicking the button.
I tried to update the view everytime user clicks the button but the count stays at zero.
The View
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("(viewModel.model.count)")
Button(action: {
self.viewModel.increment()
}) {
Text("Increment")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The ViewModel
import SwiftUI
class CounterViewModel: ObservableObject {
@ObservedObject var model = Model()
func increment() {
self.model.count += 1
}
}
The Model
import Foundation
class Model : ObservableObject{
@Published var count = 0
}
2
Answers
Following should work:
Please note:
ObservableObject and @Published are designed to work together.
Only a value, that is in an observed object gets published and so the view updated.
A distinction between model and view model is not always necessary and the terms are somewhat misleading. You can just put the count var in the ViewModel. Like:
It makes sense to have an own model struct (or class), when fx you fetch a record from a database or via a network request, than your Model would take the complete record.
Something like:
Please also note the advantages (and disadvantages) of having immutable structs as a model. But this is another topic.
Hi it’s a bad idea to use MVVM in SwiftUI because Swift is designed to take advantage of fast value types for view data like structs whereas MVVM uses slow objects for view data which leads to the kind of consistency bugs that SwiftUI’s use of value types is designed to eliminate. It’s a shame so many MVVM UIKit developers (and Harvard lecturers) have tried to push their MVVM garbage onto SwiftUI instead of learning it properly. Fortunately some of them are changing their ways.
When learning SwiftUI I believe it’s best to learn value semantics first (where any value change to a struct is also a change to the struct itself), then the View struct (i.e. when body is called), then
@Binding
, then@State
. e.g. have a play around with this:Then once you are comfortable with view data you can move on to model data which is when
ObservableObject
and singletons come into play, e.g.Note we use singletons because it would be dangerous to use
@StateObject
for model data because its lifetime is tied to something on screen we could accidentally lose all our model data which should have lifetime tied to the app running. Best to think of@StateObject
when you need a reference type in a@State
, e.g. when working asynchronously on view data when theView
struct might be init multiple times and you want to take advantage of the object beingdeinit
when the view disappears for implementing cancellation.When it comes to async features like network downloads try the new
.task
modifier. It has cancellation support built-in and with.task(id:)
it will even cancel and restart when theid
value changes. Much simpler than implementing these features yourself in an@StateObject
.