Issue #830

Since iOS 16, it is possible to define programmatic routes with `NavigationStack

I usually start by defining enum Route for all possible routes in the app. Note if your app is complex, you can define multiple Route type, each for different use case

enum Route: Hashable {
    case authors
    case books
    case search
    case settings
}

The benefit of using Route is decoupling. In a few places where we pass ObservableObject or Binding to the next navigated view, we have to think twice when we convert them to Route as it requires parameters to be of Hashable

Define a view to switch on Route

struct RouteView: View {
    let route: Route
    
    var body: some View {
        switch route {
        case .authors:
            AuthorsView()
            
        case .books:
            BooksView()
            
        case .search:
            SearchView()
            
        case .settings:
            SettingsView()
        }
    }
}

Use a Router object to hold on an array of routes

final class Router: ObservableObject {
    @Published var routes: [Route] = []
    
    func navigate(to route: Route) {
        routes.append(route)
    }
}

Declare Router for each navigation stack, and inject Router object inside environment so we can access it anywhere in the environment path

struct CompactView: View {
    @StateObject private var booksRouter = Router()
    
    var body: some View {
        TabView {
            NavigationStack(path: $booksRouter.routes) {
                BooksView()
                    .navigationDestination(for: Route.self) {
                        RouteView(route: route)
                             .environmentObject(booksRouter)
                    }
            }
            .environmentObject(booksRouter)
        }
    }
}

Note that we need to inject environmentObject inside navigationDestination too

Read more

Router

Coordinator