TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

No matching navigationDestination declaration visible

Forums > SwiftUI

Hi! I'm working on the Navigation challenge (day 46) and upgrading my Moonshot app to use NavigationLink(value:).

I successfully changed it in multiple places but I'm stuck on the one in the ContentView.

Here's the code I have for the ContentView:

//
//  ContentView.swift
//  Moonshot
//
//  Created by Paul Newman on 11/7/23.
//

import SwiftUI

struct ContentView: View {

    @State private var layout = "grid"
    let layouts = ["grid", "list"]

    let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")
    let missions: [Mission] = Bundle.main.decode("missions.json")

    var columns: [GridItem] {
        switch layout {
        case "grid":
            return [GridItem(.adaptive(minimum: 150))]
        case "list":
            return [GridItem(.flexible())]
        default:
            return [GridItem(.adaptive(minimum: 150))]
        }
    }

    var body: some View {
        NavigationStack {
            VStack {
                ScrollView {
                    LazyVGrid(columns: columns) {
                        ForEach(missions, id: \.id) {mission in
                            NavigationLink(value: mission) {
                                MissionLabel(mission: mission, layout: layout)
                            }
                        }
                    }
                    .padding([.horizontal, .bottom])
                }
                .padding(.top, 16)
                .navigationTitle("Moonshot")
                .toolbar {
                    Picker("", selection: $layout) {
                        Image(systemName: "rectangle.grid.1x2.fill").tag("list")
                        Image(systemName: "square.grid.2x2.fill").tag("grid")
                    }
                    .pickerStyle(.segmented)
                }
                .background(.darkBackground)
                .preferredColorScheme(.dark)
            }
            .navigationDestination(for: Mission.self) { mission in
                MissionView(mission: mission, astronauts: astronauts)
//                Text("\(mission.description)")
            }
        }
    }
}

#Preview {
    ContentView()
}

When I comment the MissionView inside the .navigationDestination and use the TextView instead (for the testing purposes), it works just fine. But when I try to use MissionView as a destination, I get the following message in the console:

A NavigationLink is presenting a value of type “Mission” but there is no matching navigationDestination declaration visible from the location of the link. The link cannot be activated.

Here's the MissionView code:

//
//  MissionView.swift
//  Moonshot
//
//  Created by Paul Newman on 11/14/23.
//

import SwiftUI

struct MissionView: View, Hashable {

    struct crewMember: Hashable {
        var role: String
        var astronaut: Astronaut
    }

    let mission: Mission
    let crew: [crewMember]
    let astronauts: [String: Astronaut]

    var body: some View {

        NavigationStack {
            ScrollView {
                VStack(spacing: 40) {
                    Image(mission.image)
                        .resizable()
                        .scaledToFit()
                        .containerRelativeFrame(.horizontal) { width, axis in
                            width * 0.5
                        }
                        .padding(.top, 8)
                    VStack(alignment: .leading, spacing: 16) {
                        Text("Mission highlights")
                            .font(.title.bold())
                        Text(mission.description)
                            .padding(.bottom)
                        DividerCustom()
                    }
                    .padding(.horizontal)
                }

                CrewView(mission: mission, astronauts: astronauts)
            }
        }
        .navigationTitle(mission.displayName)
        .navigationBarTitleDisplayMode(.inline)
        .background(.darkBackground)
    }

    init(mission: Mission, astronauts: [String: Astronaut]) {
        self.mission = mission
        self.astronauts = astronauts

        self.crew = mission.crew.map { member in
            if let astronaut = astronauts[member.name] {
                return crewMember(role: member.role, astronaut: astronaut)
            } else {
                fatalError("Missing \(member.name)")
            }
        }
    }

}

#Preview {

    let missions: [Mission] = Bundle.main.decode("missions.json")

    let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")

    return MissionView(mission: missions[0], astronauts: astronauts)
        .preferredColorScheme(.dark)
}

Any suggestions on how to fix the issue are appreciated ❤️

3      

@Henny  

+1 this question. I think we talked about this on Slack Paul :) I'll keep digging and let you know if I find anything

3      

I've found programmatic navigation works quite differently from what we're used to, my finding was that when I remove NavigationStack from the MissionView it is then able to pick up on that navigationDestination and finally see it.

In short just try to remove the NavigationStack from your MissionView, and note that you can still use navigationTitle modifier in it.

Here's my ContentView and MissionView in case I've missed something, let me know if it works:

//
//  ContentView.swift
//  Moonshot
//
//  Created by Maks Winters on 24.11.2023.
//

import SwiftUI

struct ContentView: View {

    let astronauts: [String: Astronaut] = Bundle.main.decode(file: "astronauts")
    let missions: [Mission] = Bundle.main.decode(file: "missions")

    @State private var path = NavigationPath()

    @State private var isGrid = false

    var body: some View {
        NavigationStack(path: $path) {
            ScrollView {
                if isGrid {
                    MissionGridView(astronauts: astronauts, missions: missions)
                } else {
                    MissionListView(astronauts: astronauts, missions: missions)
                }
            }
            .navigationDestination(for: Mission.self) { item in
                MissionView(mission: item, astronauts: astronauts)
            }
            .toolbar {
                Button("View") {
                    withAnimation {
                        isGrid.toggle()
                    }
                }
            }
            .navigationTitle("Moonshot")
            .background(.darkBackground)
            .preferredColorScheme(.dark)
        }
    }
}
//
//  MissionView.swift
//  Moonshot
//
//  Created by Maks Winters on 26.11.2023.
//

import SwiftUI

struct CrewMember {
    var role: String
    var astronaut: Astronaut
}

struct MissionView: View {

    let mission: Mission

    let crew: [CrewMember]

    let astronauts: [String: Astronaut]

    var body: some View {
        ScrollView {
            VStack {
                Image(mission.image)
                    .resizable()
                    .scaledToFit()
                    .shadow(color: .yellow, radius: 5)
                    .padding(.vertical)
                    .containerRelativeFrame(.horizontal) { width, Axis in
                        width * 0.6
                    }
                Text(mission.formattedLaunchDate)
                VStack(alignment: .leading) {
                    CustomDevider()
                    Text("Mission Highlights")
                        .font(.title.bold())
                        .padding(.bottom, 5)
                    Text(mission.description)
                    CustomDevider()
                    Text("Crew")
                        .font(.title.bold())
                        .padding(.bottom, 5)
                }
                .padding(.horizontal)
                CrewScrollView(crew: crew)
            }
        }
        .background(.darkBackground)
        .navigationTitle(mission.displayName)
    }

    init(mission: Mission, astronauts: [String : Astronaut]) {
        self.mission = mission
        self.astronauts = astronauts

        self.crew = mission.crew.map { crewMember in
            if let astronaut = astronauts[crewMember.name] {
                return CrewMember(role: crewMember.role, astronaut: astronaut)
            } else {
                fatalError("Couldn't find \(crewMember.name)")
            }
        }
    }
}

3      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.