Search code examples
swiftuiswiftui-navigationlinkswiftui-navigationstack

NavigationLink takes me to the parent view of the view with the navigation stack on it


I am new to SwiftUI and I am simply experimenting with different NavigationStack options. I have a navigation stack that shows a list of words. When the user clicks on the navigation link it shows a detailed view of that word. Very simple.

The code below doesn't work. When I click on the link, it takes me to the a screen which just flashes and it goes directly to the parent view of the view below.

var body: some View {     
        NavigationStack {
            List(dictionary, id: \.id) { wordDefinition in
                NavigationLink(wordDefinition.g_keyword, value: wordDefinition)
            }
            .navigationDestination(for: DictionaryEntry.self) { wordDefinition in
                DefinitionCardView(wordDefinition: wordDefinition)
            }
            .listStyle(PlainListStyle())
        }
    }

This code works fine.

var body: some View {
        NavigationStack {
            List(dictionary, id: \.id) { wordDefinition in
                NavigationLink(wordDefinition.g_keyword, destination: DefinitionCardView(wordDefinition: wordDefinition))
            }
            .listStyle(PlainListStyle())
        }
    }

Parent view of the above code. I am just playing around with code so please ignore any logic or optimization issues. For example SomeViewThatActuallyDoesNothing() acts like a button that if pressed, takes you to the view in question. But that's just me trying different things and I don't consider this a good design choice. However, this illogical design choice presents the problem I am facing here and curiosity got me. Why does one version work and the other doesn't?

var body: some View {
        NavigationStack {
            
            VStack(alignment: .leading) {
                
                SomeViewThatActuallyDoesNothing(searchWord: $searchWord)
                .onTapGesture {
                    isSearchFieldTapped = true;
                }
                
                NavigationLink {
                    DefinitionCardView(wordDefinition: wordOfTheDay)
                } label: {
                    SomeSampleCardView(wordOfTheDay: wordOfTheDay)
                }
                
            }
            .padding()
            .navigationDestination(isPresented: $isSearchFieldTapped) {
                MainSearchView() // This is the view with the problem (the code above)
            }
            
        }  

I am curios now why the first version is not working while the second is fine.

EDIT: Minimal Reproducible Example

import SwiftUI
import SwiftData

struct ContView: View {
    
    var body: some View {
        
        NavigationStack {
            VStack(alignment: .center) {
                
                Spacer()
                
                NavigationLink {
                    MinNOTWorkingListView()
                } label: {
                    Text("A View With a Problem")
                }
                
                NavigationLink {
                    MinWorkingListView()
                } label: {
                    Text("A Working View")
                }
                
                Spacer()
                
                NavigationLink {
                    MinCardView(wordDef: "wordOfTheDay")
                } label: {
                    Text("Some Text")
                }
                
                Spacer()
            }
            .padding()
        }
    }
}



struct MinWorkingListView: View {
    
    private var wordList: [String] = {
        var list: [String] = []
        for i in 1...5 {
            list.append("Word" + String(i))
        }
        return list
    }()
    
    var body: some View {
        NavigationStack {
            List(wordList, id: \.self) { wordDefinition in
                NavigationLink(wordDefinition, destination: MinCardView(wordDef: wordDefinition))
            }
            .listStyle(PlainListStyle())
        }
    }
}

struct MinNOTWorkingListView: View {
    
    private var wordList: [String] = {
        var list: [String] = []
        for i in 1...5 {
            list.append("Word" + String(i))
        }
        return list
    }()
    
    var body: some View {
        NavigationStack {
            List(wordList, id: \.self) { wordDefinition in
                NavigationLink(wordDefinition, value: wordDefinition)
            }
            .navigationDestination(for: String.self) { wordDefinition in
                MinCardView(wordDef: wordDefinition)
            }
            .listStyle(PlainListStyle())
        }
    }
}

struct MinCardView: View {    
    var wordDef: String
    var body: some View {
            Text(wordDef)
    }
}

Solution

  • Just like mentioned in a comment by @Sweeper, you should not nest two NavigationStacks.

    Another thing, when mixing NavigationLink(destination:label:) with NavigationLink(value:label:) like that:

    struct ContView: View {
    
        var body: some View {
            NavigationStack {
                NavigationLink(destination: {
                    NavigationLink(value: "Value") {
                        Text("To SomeView")
                    }
                }){
                    Text("Start")
                }
                .navigationDestination(for: String.self) { value in
                    SomeView(value: value)
                }
            }
        }
    
    }
    

    ..it seems to overwhelm the NavigationStack (..and it might actually be a bug).

    Solution:

    You can use several NavigationLink(value:label:)s with dedicated .navigationDestination(for:destination) modifiers for each.

    Alternatively you could introduce a new Destination enum and handle its value in a .navigationDestination(for:destination) with a switch statement:

    enum Destination: Hashable {
        case list
        case card(String)
    }
    
    struct ContView: View {
    
        var body: some View {
            NavigationStack {
                NavigationLink(value: Destination.list) {
                    Text("List")
                }
                .navigationDestination(for: Destination.self) { destination in
                    switch destination {
                    case .list:
                        ListView()
                    case .card(let wordDefinition):
                        SomeView(value: wordDefinition)
                    }
                }
            }
        }
    
    }
    
    struct ListView: View {
    
        private var wordList: [String] = {
            var list: [String] = []
            for i in 1...5 {
                list.append("Word" + String(i))
            }
            return list
        }()
    
        var body: some View {
                List(wordList, id: \.self) { wordDefinition in
                    NavigationLink(wordDefinition, value: Destination.card(wordDefinition))
                }
            }
    
    }