Search code examples
swiftuiswiftui-navigationlinkswiftui-navigationstack

NavigationStack is breaking binding


I recently tried to migrate from SwiftUI NavigationView to NavigationStack, but experienced some problems with bindings. If the view stack has changes, my bindings seem to convert into some sort of local state instead of bindings.

image alt

I was able to recreate the issue in a much simplified version of my code. In this simplified version I have @State counter in CounterView which creates a new counter each time the view is loaded. The counter is passed as a binding to EditCoutnerView. This binding is broken when if the NavigationStack changes.

To recreate this I have to navigate as follows:

  1. Counters -> CounterView -> EditCounterView
  2. Go back to Counters
  3. Counters -> OtherCounter
  4. Go back to Counters
  5. Counters -> CounterView -> EditCounterView (Binding broken, state not updated)
  6. Go back to CounterView (State not changed)
  7. CounterView -> EditCounterView (State updated)

I can't figure out why the binding is broken. Any ideas what to do?


import SwiftUI

@main
struct PlaygroundAppApp: App {
    
    @StateObject var router: Router = .init()

    var body: some Scene {
        WindowGroup {
            NavigationStack(path: self.$router.path) {
                Counters()
            }
            .environmentObject(self.router)
        }
    }
}

struct Other: Hashable {}
struct New: Hashable {}

final class Router: ObservableObject {
    @Published var path = NavigationPath()
}


struct Counters: View {
    @EnvironmentObject var router: Router

    var body: some View {
        VStack {
            NavigationLink("New Counter", value: New())
            
            NavigationLink("Other Counters", value: Other())
                .padding()
        }
        .navigationTitle("Main")
        .navigationDestination(for: New.self) { examination in
            CounterView()
        }
        .navigationDestination(for: Other.self) { examination in
            OtherCounter()
        }
    }
}


struct OtherCounter: View {
    var body: some View {
        VStack {
            NavigationLink("Counter", value: New())
        }
        .navigationTitle("Other Counter")
    }
}


struct CounterView: View {
    @State  private var counter: Int = 0
    
    var body: some View {
        VStack {
            NavigationLink("Edit counter", value: counter)
                    .padding()
                
            Text("COUNT \(self.counter)")
        }
        .navigationDestination(for: Int.self, destination: { value in
            EditCounterView(count: self.$counter)
        })
        .navigationTitle("Counter")
    }
}


struct EditCounterView: View {
    @Binding var count: Int

    var body: some View {
        VStack {
            Text("Current count \(count)")
           
            Button("increase") {
                self.count += 1
            }
            .padding()
            Button("decrease") {
                self.count -= 1
            }
        }
        .navigationTitle("Edit Counter")

    }
}

Solution

  • As lorem ipsum pointed out, NavigationLink with destination and label fixes this issue.