iosswiftuinavigationsplitviewnavigationpath

SwiftUI NavigationPath in NavigationStack(path: $path) in NavigationSplitView does not Compile


I have a simple NavigationSplitView app with a simple SwiftData model. I'm trying to use NavigationPath in order to return to the ThingListView when several layers deep. Every article I've found and three different AI coders say this code should work, but it does not compile. In the detail: below, the line "NavigationStack(path: $router.path) {" raises the error "Cannot find '$router' in scope".

import SwiftUI
import SwiftData
import Observation

@main
struct ThingNavigationPathApp: App {
    
    @State private var router = Router()
    
    var body: some Scene {
        WindowGroup {
            ThingListView()
                //.environment(router)
        }
        .modelContainer(for: [Thing.self])
        .environment(router)
    }
}

@Observable
class Router {
    var path = NavigationPath()
        
    func navigateToRoot() {
        path = NavigationPath()
    }
}

struct ThingListView: View {
    
    @Environment(\.modelContext) private var context
    @Query(sort: \Thing.name) private var things: [Thing]
    @Environment(Router.self) private var router
    
    @State private var selectedThing: Thing?
    @State private var searchText = ""
    
    var body: some View {
        NavigationSplitView {
            VStack {
                Text("Things")
                
                List(selection: $selectedThing) {
                    ForEach(things, id: \.self) { thing in
                        NavigationLink(value: thing) {
                            ThingRowView(thing: thing)
                        }
                    }
                }
                .searchable(text: $searchText)
                
                Button(action: {
                    Task {
                        //do some setup
                    }
                }, label: {
                    Text("Load Examples")
                })
            }
        } detail: {
            NavigationStack(path: $router.path) {
                VStack {
                    if let selectedThing = selectedThing {
                        ThingDetailView(selectedThing: selectedThing)
                    } else {
                        PlaceholderView()
                    }
                }
                .navigationDestination(for: Thing.self) { thing in
                    ThingEditView(thing: thing)
                }
            }//nav
        }
    }
}

And just for completeness:

@Model
final public class Thing {
    
    var name: String = ""
    var comment: String = ""
    var imageData: Data?
    var count: Int = 0
    
    init(name: String, comment: String, imageData: Data? = nil, count: Int) {
        self.name = name
        self.comment = comment
        self.imageData = imageData
        self.count = count
    }

    static let one = Thing(name: "Thing One", comment: "fruit", count: 1)
    static let two = Thing(name: "Thing Two", comment: "vegetable", count: 2 )
    static let three = Thing(name: "Thing Three", comment: "protein", count: 3)
    
}//class thing

struct ThingDetailView: View {
    @Environment(\.modelContext) private var context
    @Environment(Router.self) private var router
    
    
    let selectedThing: Thing

    @State private var newUIImage: UIImage? = nil
    @State private var disableSaveButton: Bool = false
    @State private var shouldEditPhoto: Bool = false
    
    var body: some View {
            List {
                Section {
                    NavigationLink(value: selectedThing) {
                        ThingRowView(thing: selectedThing)
                    }
                } header: {
                    Text("Thing Name")
                }
            }
            .navigationTitle(selectedThing.name)
    }
}

Any guidance would be appreciated. Xcode 16.2, iOS 18.2


Solution

  • for your code to compile and work, you need to use a @Bindable (aka gives you a Binding for path), as mentioned by @lorem ipsum. Because this is what is expected in the call NavigationStack(path: $router.path).

    So simply add this in your ThingListView:

    var body: some View {
        @Bindable var router = self.router  // <--- here
        NavigationSplitView {