BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

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)")
            }
        }
    }
}

4      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.