Problem
When a State variable in a parent View changes, SwiftUI will redraw child Views and consequently call their constructors again.
Consider the following View:
struct Parent: View {
@ObservedObject var viewModel: ParentViewModel
var body: some View {
VStack {
Child(viewModel: ChildViewModel())
}
}
}
Any state change in the Parent View (ex. a Published variable changing in its ViewModel) might cause the Child View to be redrawn, leading to its viewModel property to receive a new instance of ChildViewModel.
Expectation vs. reality
When a subview gets redrawn I expect it to still reflect the state it was previously in, however since constructor is called again with a new instance of the ViewModel, this is not the case.
What I've tried
Let me preface this by stressing that we use SwiftUI 1.0, so using the @StateObject property wrapper is not an option.
We've attempted to use a ViewModelProvider class that caches ViewModels of a certain type for a longer time, so that consecutive initializations of a certain ViewModel will return the same instance. This works perfectly if there can only be one instance of a ViewModel (and thus, View) at one time. However, once you add Views that can navigate to new instances of that same View (adding the requirement for multiple ViewModels of the same type to exist) this falls apart.
We have some additional ideas about making aforementioned pattern work, but I just can't shake the feeling there should be a better way to do this without jumping through hoops.
Code
Setting Example as the rootView of your SceneDelegate, combined with some furious tapping of the button, should trigger print statements that should show you that every now and then (not even predictably) a different instance of ChildViewModel is used.
struct Example: View {
@State var selection = 0
var body: some View {
VStack {
Button("Tapped \(selection) times") {
self.selection += 1
}
ChildView(viewModel: ChildViewModel())
ChildView(viewModel: ChildViewModel())
}
}
}
struct ChildView: View {
@ObservedObject var viewModel: ChildViewModel
var body: some View {
Text("Child")
}
}
class ChildViewModel: ObservableObject {
init() {
withUnsafePointer(to: self) {
print($0)
}
}
}