Search code examples
swiftuiswiftui-navigationlink

SwiftUI NavigationLink Console Error (SwiftUI encountered an issue when pushing aNavigationLink. Please file a bug.)


Having an issue with the above error encountered when attempting to trigger a Navigation Link Programatically. Upon clicking the link, the wrong destination array element will load sporadically (vs the label clicked on), and of course the above error shows.

When I trigger the Navigation Link non programmatically the error does not occur, but I have another method that I need to run when the user clicks this link.

I have read through several questions, like here and here, but cannot find the cause in my case.

Model Struct:

struct Place: Identifiable {
var id: UUID = UUID()
var name: String = ""
var testBool: Bool = false

}

Class for environment object: (Production project has many properties and methods - essentially this is the ViewModel)

class UserPlaces: ObservableObject {
@Published var placesArray: [Place] = [Place]([Place(name: "Christian", testBool: false),Place(name: "Catherine", testBool: true)])

}

Class for navigation boolean: (Production project has many properties here that I want to keep in one class)

class Navigation: ObservableObject {
@Published var showPlaceDetail: Bool = false

}

ContentView: (Parent view where NavigationView is located)

import SwiftUI

struct ContentView: View {

var body: some View {
    
    NavigationView {
        
        ScrollView {
            
            Text("Hello, world!")
                .padding()
            CardsGrid2()
            
        } // End ScrollView
    } // End Navigation View
} // End body

} // End Struct

CardsGrid: (In this view the NavigationLink is not programmatically triggered and the console error does not show.)

import SwiftUI

struct CardsGrid: View { @EnvironmentObject var userPlaces: UserPlaces

var gridLayout: [GridItem] = [GridItem(.flexible(minimum: 40), spacing: -36),GridItem(.flexible(minimum: 40), spacing: -36)]

var body: some View {
    
    LazyVGrid(columns: gridLayout, spacing: 10) {
        
        ForEach(userPlaces.placesArray) { placeCard in
                
            NavigationLink(
                destination: Text("Destination")
                , label: { Text("Label") }
            )

        } // End ForEach
    
    } // End LazyVGrid
} // End body

} // End struct

CardsGridProgrammatic: (In this view the NavigationLink is programmatically triggered and the console error shows.)

import SwiftUI

struct CardsGridProgrammatic: View {

@EnvironmentObject var userPlaces: UserPlaces
@EnvironmentObject var navigation: Navigation

var gridLayout: [GridItem] = [GridItem(.flexible(minimum: 40), spacing: -36),GridItem(.flexible(minimum: 40), spacing: -36)]

var body: some View {
    
    LazyVGrid(columns: gridLayout, spacing: 10) {
        
        ForEach(userPlaces.placesArray) { placeCard in
            NavigationLink(destination: Text("Destination"), isActive: $navigation.showPlaceDetail) { EmptyView() }
            .isDetailLink(false)

                    Button(action: {

                        navigation.showPlaceDetail = true
                        // Other required method call here

                    }, label: { Text("label") })

        } // End ForEach
    
    } // End LazyVGrid
    
} // End body

} // End struct

Additionally:

  • I tried wrapping the ForEach in a Form and a List - doesn't work.
  • Moving the ScrollView did work - BUT I need the scrollview in the content view because I have other views that I want to scroll as well.
  • I thought it may have something to do with the LazyGrid - perhaps it doesn't play nice with NavLink, but couldn't find anything there either.

How can I fix this?


Solution

  • As your links suggested move the NavigationLink out of the ForEach. The following works well for me on macos 12.01, xcode 13.1, target ios 15 and macCatalyst 12. Tested on mac Monterey and iPhone ios 15 devices. It may be different on older systems. To be sure, this is the code I used for testing:

    import SwiftUI
    
    @main
    struct TestApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
    struct Place: Identifiable {
        var id: UUID = UUID()
        var name: String = ""
        var testBool: Bool = false
    }
    
    class UserPlaces: ObservableObject {
        @Published var placesArray: [Place] = [
            Place(name: "Christian", testBool: false),
            Place(name: "Catherine", testBool: true)]
    }
    
    class Navigation: ObservableObject {
        @Published var showPlaceDetail: Bool = false
    }
    
    
    struct ContentView: View {
        @StateObject var userPlaces = UserPlaces()
        @StateObject var navigation = Navigation()
        
        var body: some View {
            NavigationView {
                ScrollView {
                    Text("Hello, world!").padding()
                    CardsGrid()
                }
            }
            .environmentObject(userPlaces)
            .environmentObject(navigation)
        }
    }
    
    struct CardsGrid: View {
        @EnvironmentObject var userPlaces: UserPlaces
        @EnvironmentObject var navigation: Navigation
        
        @State var selection: Place?  // <--- here
        
        var gridLayout: [GridItem] = [GridItem(.flexible(minimum: 40), spacing: -36),
                                      GridItem(.flexible(minimum: 40), spacing: -36)]
        
        var body: some View {
            LazyVGrid(columns: gridLayout, spacing: 10) {
                ForEach(userPlaces.placesArray) { placeCard in
                    Button(action: {
                        selection = placeCard   // <--- here
                        navigation.showPlaceDetail = true
                        // Other required method call here
                    }, label: { Text("\(placeCard.name) label") })
                }
            }
            // --- here
            NavigationLink(destination: Text("Destination \(selection?.name ?? "")"),
                           isActive: $navigation.showPlaceDetail) { EmptyView() }.isDetailLink(false)
        }
    }