Search code examples
swiftuiswiftui-navigationlink

NavigationLink will not enable


I have a situation where I need to have something selectable from a list. However the thing needs to be an optional. The user is not required to pick one but they might, or might not. If they do pick one they might clear it later.

Based on my somewhat limited SwiftUI knowledge I'm hoping the sample code below is as simple as can be to show the problem I'm running into.

struct Thing: Hashable {
    let name: String
}

struct ContentView: View {
    @State private var selectedThing: Thing?
    var body: some View {
        Form {
            NavigationStack {
                NavigationLink(value: selectedThing) {
                    HStack {
                        Text("Selected Thing:")
                        Spacer()
                        Text("\(selectedThing?.name ?? "no thing selected")")
                    }
                }
                .navigationDestination(for: Thing.self) { _ in
                    Things(selectedThing: $selectedThing)
                }
            }
        }
        .padding()
    }
}

// this is the detail view, there will be a list of Things here eventually
struct Things: View {
    @Binding var selectedThing: Thing?
    var body: some View {
        Text("hello thing: \(selectedThing?.name ?? "")")
    }
}

But when ContentView renders the row with the chevron is disabled.

enter image description here

How do I get this row to be enabled? Can I accomplish this in SwiftUI with an optional like that? If so, what am I missing?

ps. I googled "swiftui navigationlink disabled" and I couldn't find anything for this particular case. There were some with "be sure to use NavigationStack instead of NavigationView"

Using Xcode 15.2.


Solution

  • Firstly, you should put the NavigationStack outside of the Form.

    Secondly, you are calling this initialiser of NavigationLink. The value: parameter takes a P?, so here P is Thing, not Thing?. As the documentation says, when value is nil, the navigation link is disabled.

    What you probably intended to do, is for P to be Thing?, and .navigationDestination(for: Thing?.self) instead of .navigationDestination(for: Thing.self).

    You can wrap selectedThing in an additional layer of Optional. This ensures value is never nil (but the value wrapped in it can be).

    NavigationStack {
        Form {
            NavigationLink(value: Optional.some(selectedThing)) {
                HStack {
                    Text("Selected Thing:")
                    Spacer()
                    Text("\(selectedThing?.name ?? "no thing selected")")
                }
            }
            .navigationDestination(for: Thing?.self) { _ in
                Things(selectedThing: $selectedThing)
            }
        }
    }
    

    Side note: you might be reinventing the .navigationLink picker style. Consider using a Picker:

    Picker("Selected Thing:", selection: $selectedThing) {
        Text("None").tag(nil as Thing?)
        Text("One").tag(Thing(name: "One") as Thing?)
        Text("Two").tag(Thing(name: "Two") as Thing?)
    }
    .pickerStyle(.navigationLink)
    

    Notice that we also wrap the tags in an additional layer of Optional.