I’ve recently encountered a weird instantiation behaviour where the wrapped value of a @StateObject could be created twice. It happens when the @StateObject is instantiated in the init function of a View. Only if the View is embedded in a NavigationStack AND there is some state driven component inside the View.
Here’s a self contained code sample that demonstrates that. Does anybody know what’s going on?
Thanks.
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
MainView(resolver: makeDependency)
}
}
}
func makeDependency() -> MyDependency {
print("makeDependency") // printed twice
return MyDependency(value: "5")
}
class MyDependency: ObservableObject {
@Published
var value: String
init(value: String) {
self.value = value
}
}
struct MainView: View {
var resolver: () -> MyDependency
@State var text = ""
init(resolver: @escaping () -> MyDependency) {
self.resolver = resolver
}
var body: some View {
NavigationStack { // if commented, no double dep resolution
WithDependencyView(resolver: resolver) { dependency in
VStack {
Text(dependency.value)
TextField("Input", text: $text, axis: .horizontal) // if commented, no double dep resolution
}
}
}
}
}
// equivalent to WithViewStore
struct WithDependencyView<Content: View>: View {
@StateObject var dependency: MyDependency
let content: (MyDependency) -> Content
init(
resolver: @escaping () -> MyDependency,
@ViewBuilder content: @escaping (MyDependency) -> Content
) {
self._dependency = StateObject(wrappedValue: resolver())
self.content = content
}
var body: some View {
self.content(self.dependency)
}
}
I actually ran into the same issue a few days ago. In my case, nesting a view in a TabView is what caused the issue.
I'm pretty sure this is a bug, I ended up filing a feedback about it (FB11936191) with this sample project where you can clearly see the ViewModel, MyDependancy in your case is initialized multiple times when nested in a TabView and only initialized once when it is not nested in a TabView.
I'm pretty sure this is a bug since the documentation say
SwiftUI creates a new instance of the object **only once** for each instance of the structure that declares the object. When published properties of the observable object change, SwiftUI updates the parts of any view that depend on those properties:
Sorry I don't have a solution for you but at least you'll know that you aren't the only one running into this
Made a mistake and deleted my response while fixing a typo, so I'm reposting it:
From my experience, NavigationStack sometimes scouts the view tree ahead and on its own (independently of view invalidation), probably to look for navigationDestination or NavigationLinks' values. So this is maybe what you're experiencing.
@Lontronix would you mind providing the direct link to the feedback you mentioned (FB11936191)? I am experiencing this issue on a project and curious to see if there are any recommendations on a workaround here. Thanks in advance!
Unless you work for Apple there's no way to link to a Feedback issue unless you're the original reporter. If the original reporter also linked the issue with OpenRadar, you may find it there, but it's not always possible to post the follow up messages.
I suspect it's not an issue with StateObject but due to the fact the view might be created twice (as pointed out by @tgrapperon too). I think the behavior might be considered as SwiftUI implementation details and it's harmless in this case (it's a brand new StateObject so creating it twice doesn't lose any thing). If the view was recreated after the state object was modified (e.g. user interacted with the UI), that would be an issue. But I haven't seen such an issue myself.