SwiftUI 4.0 - Passing a Binding via .navigationDestination(for: , destination: )
Asked Answered
P

2

6

How do I pass a Binding via the new .navigationDestination(for: , destination: )?

import SwiftUI

enum TestEnum: String, Hashable, CaseIterable {
    case first, second, third
}

struct ContentView: View {
    
    @State private var test: TestEnum = .first

    var body: some View {
        NavigationStack {
            VStack {
                NavigationLink(value: test, label: {
                    Text(test.rawValue)
                })
            }
            // This does not work, as it won't allow me to use $caze
            .navigationDestination(for: TestEnum.self, destination: { caze in
                SecondView(test: $caze)
            })
        }
    }
}

struct SecondView: View {
    
    @Environment(\.presentationMode) var presentationMode
    @Binding var test: TestEnum
    
    var body: some View {
        ForEach(TestEnum.allCases, id: \.self) { caze in
            Button(action: {
                test = caze
                presentationMode.wrappedValue.dismiss()
            }, label: {
                Text(caze.rawValue)
            })
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

In SwiftUI 3.0 I'd simply use:

NavigationLink(destination: SecondView(test: $test), label: {
   Text(test.rawValue)
})

Is this still the correct approach, as we cannot pass a Binding yet?

Not really interested in complex workarounds like using an EnvironmentObject and passing an index, as the SwiftUI 3.0 approach works fine.

However, if there is a proper way of passing a Binding via .navigationDestination(for: , destination: ) I'll happily use it.

Presage answered 31/10, 2022 at 22:38 Comment(4)
I think there is nothing wrong with using NavigationLink(destination: SecondView(test: $test), label: { Text(test.rawValue) }) with the NavigationStack instead of the navigationDestination. NavigationView is deprecated but not NavigationLink(destination: ..)Eriha
There is no "proper" way that isn't what you are using now. Binding isn't HashableLeverett
Supposing that one actually needs to use .navigationDestination with a Binding, for example in a programmatic navigation with bound properties... Is there a way to make Binding<Stuff> Hashable?Diffusivity
The problem I found with using NavigationLink(destination:label:) is that it does not add the destination to the Navigation Stack path.Auricular
H
-1

In the Food Truck sample project where they compute a binding like this:

.navigationDestination(for: Donut.ID.self) { donutID in
    DonutEditor(donut: model.donutBinding(id: donutID))
}

Fyi the binding doesn't need to be computed from a model, it can just be from a state.

You should be aware that since model is really self.model this creates a dependency on self so this closure will be recomputed whenever body is called and the destination is visible. Which is different from when donutID is the only thing used and the closure is only called once. However with the first way whenever the data changes every detail view is re-init for every item in the array.

You would expect the below to work but there is a bug that the selection is not set to nil when going back and programatically setting the selection in a button highlights the row but does not navigate:

@State var selectedItem: Item.ID?
...
NavigationStack {
    List(selection: $selectedItem, $items) { $item in
         NavigationLink {
             Detail(item: $item)
         } label: {
             Text("Row text")
         }
Hendiadys answered 7/10 at 12:10 Comment(0)
W
-2

According to this doc:

https://developer.apple.com/documentation/charts/chart/navigationdestination(for:destination:)/

[WRONG: Where destination get passed from NavigationLink, but in the doc they use the NavigationLink("Mint", value: Color.mint) version of the NavigationLink. I don't know if this make any difference.

You are using the NavigationLink(value:label:)]

EDIT: I insist, after further investigation, that you are using the ViewModifier wrong.

Read the doc that I pointed above. The viewModifier signature is:

func navigationDestination<D, C>(
    for data: D.Type,
    destination: @escaping (D) -> C
) -> some View where D : Hashable, C : View

Note D is Hashable, not State

So, when you pass $caze you are not passing the @State var above, but the NavigationLink(value: test... that is not a @State wrapper. Is the generic D ... a single value, so $caze is not a Binding to the State.

You can pass normally $test, from that is a Binding to the State test. But the parameter inside the closure .navigationDestination( value in ... is not.

Wonderwork answered 1/11, 2022 at 0:20 Comment(2)
Your answer is completely unrelated to the question. Also, NavigationLink("", value:) is the simplified version of NavigationLink(value:label:). The former has a simple Text and the latter can be a complex view for the NavigationLink’s label.Presage
Sorry for trying to answer you. I did my best.Wonderwork

© 2022 - 2024 — McMap. All rights reserved.