0

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")

    }
}
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • 2
    Binding doesn’t belong in a navigationDestination, it is incorrect conceptually (the body redraws when it changes) – lorem ipsum Jan 31 '23 at 17:03
  • @loremipsum so what is the best practice for editing a value in a subview using navigationDestination? Should I use an ObservableObject? – Dunderklaepp Feb 02 '23 at 08:39
  • NavigationLink with destination and label – lorem ipsum Feb 02 '23 at 10:25
  • 1
    From what I can tell, if you are using NavigationStack the only way to have the navigation path updated is to use NavigationLink(value and navigationDestination(for. If you use NavigationLink with destination and label the path isn't updated and you can't use it to pop back to a specific location. – Jeff Zacharias Feb 12 '23 at 18:00

2 Answers2

1

If you go to New Counter->Edit Counter->Increase and it goes to 1, then go back, back and again go to New Counter you'll see its back at zero. The state is lost when you exit back out of the New Counter screen (CounterView). If you want to maintain state across screens you need to move it up to a common parent. It's probably not a good idea to use a navigation value as a counter usually it's an ID that doesn't change. If you say what you are trying to achieve perhaps we could come up with a better data model.

malhal
  • 26,330
  • 7
  • 115
  • 133
0

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