Using @Environment for a router implementation...

Been messing with this for a while... And cannot figure things out...

Have a basic router implemented...

import Foundation
import SwiftUI
enum Route: Hashable {
    case profile(userID: String)
    case settings
    case someList
    case detail(id: String)
}

@Observable
class Router {
    var path = NavigationPath()
    private var destinations: [Route] = []
    var currentDestination: Route? {
        destinations.last
    }
    var navigationHistory: [Route] {
        destinations
    }
    func navigate(to destination: Route) {
         destinations.append(destination)
         path.append(destination)
     }
}

And have gotten this to work with very basic views as below...

import SwiftUI

struct ContentView: View {
    @State private var router = Router()    
    var body: some View {
        NavigationStack(path: $router.path) {
            VStack {
                Button("Go to Profile") {
                    router.navigate(to: .profile(userID: "user123"))
                }                
                Button("Go to Settings") {
                    router.navigate(to: .settings)
                }                
                Button("Go to  Listings") {
                    router.navigate(to: .someList)
                }
                .navigationDestination(for: Route.self) { destination in
                    destinationView(for: destination)
                }
            }
        }
        .environment(router)
    }
    
    @ViewBuilder
    private func destinationView(for destination: Route) -> some View {
        switch destination {
        case .profile(let userID):
            ProfileView(userID: userID)
        case .settings:
            SettingsView()
        case .someList:
            SomeListofItemsView()            
        case .detail(id: let id):
            ItemDetailView(id: id)
        }
    }
}

#Preview {
    ContentView()
}

I then have other views named ProfileView, SettingsView, SomeListofItemsView, and ItemDetailView....

Navigation works AWESOME from ContentView. Expanding this to SomeListofItemsView works as well... Allowing navigation to ItemDetailView, with one problem... I cannot figure out how to inject the Canvas with a router instance from the environment, so it will preview properly... (No idea if I said this correctly, but hopefully you know what I mean)

import SwiftUI

struct SomeListofItemsView: View {
    
    @Environment(Router.self) private var router
    var body: some View {
        VStack {
            Text("Some List of Items View")
 
            Button("Go to Item Details") {
                router.navigate(to: .detail(id: "Test Item from List"))
            }
        }
    }
}

//#Preview {
//    SomeListofItemsView()
//}

As you can see, the Preview is commented out. I know I need some sort of ".environment" added somewhere, but am hitting a wall on figuring out exactly how to do this.

Everything works great starting from contentview (with the canvas)... previewing every screen you navigate to and such, but you cannot preview the List view directly.

I am using this in a few other programs, but just getting frustrated not having the Canvas available to me to fine tune things... Especially when using navigation on almost all views... Any help would be appreciated.

Answered by Developer Tools Engineer in 847650022

Hi,

I believe this should work:

#Preview {
    SomeListofItemsView()
        .environment(Router())
}

and if that works then you should be able to generalize it like so:

struct Routered: PreviewModifier {
    static func makeSharedContext() async throws -> Router {
        Router()
    }

    func body(content: Content, context: Router) -> some View {
        content
            .environment(context)
    }
}

extension PreviewTrait where T == Preview.ViewTraits {
    static var routered: Self = .modifier(Routered())
}

and the preview then can use this trait:

#Preview(traits: .routered) {
    SomeListofItemsView()
}
Accepted Answer

Hi,

I believe this should work:

#Preview {
    SomeListofItemsView()
        .environment(Router())
}

and if that works then you should be able to generalize it like so:

struct Routered: PreviewModifier {
    static func makeSharedContext() async throws -> Router {
        Router()
    }

    func body(content: Content, context: Router) -> some View {
        content
            .environment(context)
    }
}

extension PreviewTrait where T == Preview.ViewTraits {
    static var routered: Self = .modifier(Routered())
}

and the preview then can use this trait:

#Preview(traits: .routered) {
    SomeListofItemsView()
}

Sometimes it's so obvious after the fact. Thanks for taking the time. Works perfectly!

Using @Environment for a router implementation...
 
 
Q