iosswiftui

Navigation Link view not loading in; Black Screen


Trying to load a view in a loop using NavLink but when I click on the label, it just goes to a black screen for a few seconds and goes back to the root view. The code compiles fine in preview and there are no error codes. When I run the simulator I get this message in the debugger:

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

I have tried moving the navDest to different points within the navStack but it doesn't solve the problem. Code is:

struct ContentView: View {
    @State private var gridList = true
    
    let missions = MissionC()
    
    let layout = [
        GridItem(.adaptive(minimum: 150))
    ]
    
    var body: some View {
        NavigationStack {
            Group {
                if gridList {
                ScrollView {
                    LazyVGrid(columns: layout) {
                        ForEach(missions.missionItem) { mission in
                            NavigationLink(value: mission) {
                                NavLink(mission: mission)
                            }
                        }
                    }
                    .navigationDestination(for: Missions.self) { mission in
                    MissionDetails(missions: mission)
                    }
                    .padding([.horizontal, .bottom])
                }
                
                } else {
                    List {
                        ForEach(missions.missionItem) {mission in
                            NavigationLink(value: mission) {
                                NavLink2(mission: mission)
                            }
                        }
                        .listRowBackground(Color.lightBackground)
                    }
                    .navigationDestination(for: Missions.self) { mission in
                    MissionDetails(missions: mission)
                    }
                    .scrollContentBackground(.hidden)
                }
            
            
            }
            .navigationTitle("Moonshot")
            .background(.darkBackground)
            .preferredColorScheme(.dark)
            .toolbar {
                Button(gridList ? "Grid View" : "List View", systemImage: gridList ? "rectangle.grid.2x2": "list.bullet") {
                    gridList.toggle()
                }
            }
        }
    }
}

Missions struct and MissionC class are:

struct Missions: Identifiable, Codable, Hashable {
    struct Crew: Codable, Hashable {
        let name: String
        let role: String
    }
    
    let id: Int
    let launchDate: Date?
    let crew: [Crew]
    let description: String
    
    var displayName: String {
        "Apollo \(id)"
    }
    
    var image: String {
        "apollo\(id)"
    }
    
    var getLaunchDate: String {
        guard let a = launchDate else {
            return "N/A"
        }
        
        return "\(a.formatted(date: .abbreviated, time: .omitted))"
    }
}


class MissionC {
    var missionItem: [Missions] = Bundle.main.decode("missions")
}

Been trying to figure this out since yesterday, rebuilt the content view code from scratch and didn't find anything wrong. Tried loading in the errant view a bunch of other ways and it worked so I don't think the problem is there. Also, tried a different view within the NavLink and it worked so I am stumped. Seems there is something weird going on in the interaction between that specific type of NavLink and that specific view. Also tried cleaning cache, build folder, deleting derivatives etc. None of which worked.

Edit: Adding in the relevant views. MissionDetails is:

struct MissionDetails: View {
    var missions: Missions
    let astronauts = AstronautC()
    
    @State private var showingAlert = false
    @State private var alertTitle = ""
    @State private var alertMessage = ""
    @State private var frostedOver = false
    
    var body: some View {
        NavigationStack {
            ZStack {
                ScrollView {
                    VStack {
                        Image(missions.image)
                            .resizable()
                            .scaledToFit()
                            .containerRelativeFrame(.horizontal) {size, axis in
                                size * 0.8
                            }
                        
                        HStack{
                            Text("Launch date:")
                                .font(.title3)
                                .bold()
                            
                            Text(missions.getLaunchDate)
                        }
                        
                    }
                .padding(.top)
                
                    rectangle()
                
                    VStack(alignment: .leading) {
                        
                        Spacer()
                        
                        Text(missions.description)
                            .padding(.horizontal)
                        
                        rectangle()
                    }
                        
                        Text("Crew")
                            .font(.title3)
                            .bold()

                    VStack(alignment: .leading) {
                            ForEach(missions.crew, id: \.self) { crew in
                                Image(crew.name)
                                    .resizable()
                                    .scaledToFit()
                                    .clipShape(.capsule)
                                    .overlay(
                                        Capsule()
                                            .strokeBorder(.white, lineWidth: 1)
                                    )
                                
                                let person = astronauts.astronautItem[crew.name]
                                
                                Button {
                                    alertTitle = "\(person?.name ?? "")"
                                    alertMessage = "\(person?.description ?? "")"
                                    
                                    withAnimation {
                                        frostedOver = true
                                    }
                                    
                                    showingAlert = true
                                } label: {
                                    Text(person?.name ?? "")
                                        .font(.headline)
                                }
                                
                                Text(crew.role)
                            }
                }
                .padding([.horizontal, .bottom])
            }
            .navigationTitle(missions.displayName)
            .navigationBarTitleDisplayMode(.inline)
            .alert(alertTitle, isPresented: $showingAlert) {
                Button("Ok") {
                    withAnimation {
                        frostedOver = false
                    }
                    showingAlert = false
                }
            } message: {
                Text(alertMessage)
            }
            .background(.darkBackground)
                
                if frostedOver {
                    Color(.clear)
                        .background(.ultraThinMaterial)
                        .ignoresSafeArea()
                }
            }
        }
    }
}

#Preview {
    let missions = MissionC()
    
    MissionDetails(missions: missions.missionItem[0])
        .preferredColorScheme(.dark)
}

NavLink is:

struct NavLink: View {
    var mission: Missions
    
    var body: some View {
        VStack {
        Image(mission.image)
            .resizable()
            .scaledToFit()
            .frame(width: 100, height: 100)
            .padding()
        
        VStack {
            Text(mission.displayName)
                .font(.headline)
                .foregroundStyle(.white)
            
            Text(mission.getLaunchDate)
                .font(.caption)
                .foregroundStyle(.gray)
        }
        .padding(.vertical)
        .frame(maxWidth: .infinity)
        .background(.lightBackground)
    }
    .clipShape(.rect(cornerRadius: 8))
    .overlay(
        RoundedRectangle(cornerRadius: 10)
            .stroke(.lightBackground)
    )    
    }
}


#Preview {
    let preview = MissionC()
    
    NavLink(mission: preview.missionItem[0])
}

NavLink2 is:

struct NavLink2: View {
    var mission: Missions
    
    var body: some View {
        HStack(spacing: 40) {
            Image(mission.image)
                .resizable()
                .scaledToFit()
                .frame(width: 60, height: 60)
                .padding()
            
            VStack {
                Text(mission.displayName)
                    .font(.headline)
                    .foregroundStyle(.white)
                
                Text(mission.getLaunchDate)
                    .font(.caption)
                    .foregroundStyle(.gray)
            }
        }
    }
}


#Preview {
    let preview = MissionC()
    
    NavLink2(mission: preview.missionItem[0])
        .preferredColorScheme(.dark)
}


Solution

  • The error you're getting clearly explains it. The navigation link is looking for a .navigationDestination in the NavigationStack of MissionDetails (in order to resolve the value), and there isn't any.

    To fix it, remove the extra NavigationStack from MissionDetails.

    Since MissionDetails has a parent NavigationStack, you can still use .navigationTitle just fine.