Search code examples
swiftswiftuiswiftui-navigationlinkswiftui-navigationstack

SwiftUI NavigationDestination appends View so it is before the current active View


In the code below when i press on a NavigationLink to go to a HeroView, I get an unexpected result. The view is temporarily added and then removed. When i go back from the HeroList to go to ContentView, it navigates to the HeroView i expected to reach from clicking on a hero in the HeroList View.

The example code below recreates this problem. Any help would be greatly appreciated.

struct ContentView: View {
    var body: some View {
        NavigationStack {
            NavigationLink("Click here to see the list of heroes") {
                HeroList()
            }
        }
    }
}
struct HeroList: View {
    @State private var heroes: [Hero] = [Hero(id: 0, name: "Hercules"), Hero(id: 1, name: "Superman")]
    
    var body: some View {
        ScrollView {
            ForEach(heroes, id:\.id) { hero in
                NavigationLink(value: hero) {
                    // Problem happens when the navigation link is pressed
                    Text("Find out more about: \(hero.name)")
                        .padding(.bottom)
                }
            }
        }
        .navigationDestination(for: Hero.self) { hero in
            HeroView(hero: hero)
        }
    }
}
struct HeroView: View {
    @ObservedObject var hero: Hero
    var body: some View {
        Text(hero.name)
            .font(.title)
    }
}
// Custom Hero type
class Hero: Identifiable, Hashable, ObservableObject {
    let id: Int
    var name: String
    
    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
    
    static func == (lhs: Hero, rhs: Hero) -> Bool {
        return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }
}

Solution

  • Try this approach using two navigationDestination, one for the HeroList() and another one for the HeroView(...). Also simplified the struct Hero:

    struct ContentView: View {
        var body: some View {
            NavigationStack {
                NavigationLink(value: "HeroList") {
                    Text("Click here to see the list of heroes")
                }
                .navigationDestination(for: String.self) { _ in   // <-- here
                    HeroList()
                }
            }
        }
    }
    
    struct HeroList: View {
        @State private var heroes: [Hero] = [Hero(id: 0, name: "Hercules"), Hero(id: 1, name: "Superman")]
        
        var body: some View {
            ScrollView {
                ForEach(heroes) { hero in
                    NavigationLink(value: hero) {
                        Text("Find out more about: \(hero.name)")
                            .padding(.bottom)
                    }
                }
            }
            .navigationDestination(for: Hero.self) { hero in
                HeroView(hero: hero)
            }
        }
    }
    
    struct HeroView: View {
        var hero: Hero   // <-- here
        
        var body: some View {
            Text("-----HeroView-----")
            Text(hero.name).font(.title)
        }
    }
    
    struct Hero: Identifiable, Hashable {  // <-- here
        let id: Int
        var name: String
    }