Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - IOS 16 NavigationStack - @StateObject init called twice

Description

I am trying to adapt my application to the new NavigationStack introduced in IOS 16. I ended up with a strange behaviour when I have a @StateObject variable in one of my views.

When I navigate (using the new .navigationDestination() modifier) to a new view that has a @StateObject the init() block of that object will run twice.

The body of the view looks like this:

VStack {
    Text("Param: \(intParam) and \(viewModel.someData)")
            
    Button("Do Something") {
        viewModel.buttonTapped()
    }
}

BUT

If I remove the Button element the init() of the @StateObject will only run once.

Also

If I use the older NavigationLink(title:destination:) element to navigate to the new page it will run the @StateObject init() once.

Full Code

struct ContentView: View {
    
    var body: some View {
        NavigationStack {
            VStack {
                NavigationLink(value: 16) {
                    Text("Tap Me (IOS 16)")
                }
                NavigationLink("Tap Me (Older)") {
                    Page2(intParam: 45)
                }
            }
            .navigationDestination(for: Int.self) { i in
                Page2(intParam: i)
            }
            .navigationTitle("Navigation")
        }
    }
}

struct Page2: View {
    @StateObject var viewModel = ViewModel()
    let intParam: Int
    
    init(intParam: Int) {
        self.intParam = intParam
        print("Page2 view created")
    }
    
    var body: some View {
        VStack {
            Text("Param: \(intParam) and \(viewModel.someData)")
            
            Button("Do Something") {
                viewModel.buttonTapped()
            }
        }
    }
}

extension Page2 {
    @MainActor class ViewModel: ObservableObject {
        @Published var someData = "something"
        
        init() {
            print("Page2 viewmodel created")
        }
        
        func buttonTapped() {
            print("do something")
        }
    }
}

Do you have any idea for the reason of this behaviour?

like image 430
Ákos Morvai Avatar asked May 29 '26 21:05

Ákos Morvai


1 Answers

Not a bug, but it can certainly be confusing for developers. When using the destination method of the NavigationStack to navigate to other views, SwiftUI may indeed call the closure code inside navigationDestination multiple times (mostly two times). However, it uses the result of the last call to generate the next layer of views. This means that no matter how many StateObject instances you create, only the last generated instance will be used in the target view. The other instances will be automatically destroyed.

To be precise, this is not creating multiple instances of the same StateObject, but rather executing the operation of creating views multiple times and only keeping the result of the last creation.

As developers, we only need to know that when accessing the next layer of views, your StateObject instance is unique, and it remains unique when navigating to more layers.

This also tells developers from another perspective not to do any expensive operations in init, but to place such operations in onAppear or task.

As for the issue mentioned in the reply that onAppear may be called multiple times (when returning to the view), it can be solved by adding a @State flag.

like image 131
Bob Xu Avatar answered May 31 '26 11:05

Bob Xu



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!