In short - when construction a custom SwiftUI View with a ViewModel ("MyView"/"MyViewModel") like this:
struct ContentView: View {
var body: some View {
MyView(viewModel: MyViewModel())
}
}
Why is this:
struct MyView: View {
@StateObject var viewModel: MyViewModel
var body: some View {
Text("Hello world!")
}
}
Not the same as this:
struct MyView: View {
@StateObject var viewModel: MyViewModel
init(viewModel: MyViewModel) {
self._viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
Text("Hello world!")
}
}
When debugging, it looks like the second option causes new instances of MyViewModel
to be created every time MyView
is reconstructed by SwiftUI. But in the first option, the StateObject wrapper seems to do its job, and makes sure only one instance of MyViewModel
is created for all reconstructions of MyView
.
Is there some extra SwiftUI magic applied to using the View's default memberwise initializer VS a custom one? Perhaps by the ViewBuilder?
Below is a simple example app, MyApp.swift, to see the behaviour in action.
//
// MyApp.swift
//
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@AppStorage("redBackground") private var redBackground: Bool = false
var body: some View {
ZStack {
// Flipping "redBackground" will cause a reconstruction of the view hierarchy
if redBackground {
Color.red
} else {
Color.green
}
MyView(viewModel: MyViewModel())
}
}
}
final class MyViewModel: ObservableObject {
init() {
print("MyViewModel.init")
}
}
struct MyView: View {
@StateObject var viewModel: MyViewModel
@AppStorage("redBackground") private var redBackground: Bool = false
// WARNING: Uncommenting this causes the view model to be recreated every reconstruction of the view!
// init(viewModel: MyViewModel) {
// self._viewModel = StateObject(wrappedValue: viewModel)
// }
var body: some View {
VStack {
Button("Toggle background") {
redBackground = !redBackground
}
}
}
}