Using @Environment with TabView

Let me ask the general question first, then explain the context...

Each Tab of a TabView defines a separate View hierarchy. (I'm assuming that the root view of each Tab defines its own NavigationStack.) Since an @Environment is supposed to serve data to the child views in its view hierarchy, does this mean that it is possible to define Environments in each tab's root view with the same name (i.e. key) but different values? (I.e., I want a subview to access an environment value for the current view hierarchy without requiring that the subview have any knowledge of which hierarchy it is being called from.)

The actual use case has to do with using @Environment in a tabbed application to inject a router in subviews. (Each Tab has its own NavigationStack and its own NavigationPath.)

I have an @Observable router class which manages a NavigationPath.. The root view of each Tab in the application has its own instance of that router object (and hence, it's own NavigationPath). I want to inject that router into all of the subviews in each Tab's view hierarchy, so that I can use path-based navigation.

My current implementation injects the router throughout the view hierarchies via constructor injection. This works, but is a real pain and includes a bunch of duplicate code. I would like to use @Environment injection instead, but this can only work if @Environment stores its EnvironmentValues on a per view-hierarchy (rather than a per-application) basis.

So,

  • can this approach work?
  • what experience can you share concerting router-based navigation in a TabView-based app?

Thanks.

Answered by DTS Engineer in 871228022

Each Tab of a TabView defines a separate View hierarchy. (I'm assuming that the root view of each Tab defines its own NavigationStack.) Since an @Environment is supposed to serve data to the child views in its view hierarchy, does this mean that it is possible to define Environments in each tab's root view with the same name (i.e. key) but different values? (I.e., I want a subview to access an environment value for the current view hierarchy without requiring that the subview have any knowledge of which hierarchy it is being called from.) The actual use case has to do with using @Environment in a tabbed application to inject a router in subviews. (Each Tab has its own NavigationStack and its own NavigationPath.)

Environment values are scoped to their view hierarchy. This means that an Environment value set at a tab's root view only affects that tab's subtree.

You can define the same environment key in multiple tabs with different values. Each child views automatically resolve the environment value from their closest enclosing provider in their hierarchy. So a subview in Tab A will get Tab A's environment value, while a subview in Tab B will get Tab B's environment value.

For example:

struct RouterKey: EnvironmentKey {
    static let defaultValue: Router = Router()
}

extension EnvironmentValues {
    var router: Router {
        get { self[RouterKey.self] }
        set { self[RouterKey.self] = newValue }
    }
}

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            TabView {
                Tab("Home", systemImage: "house") {
                    HomeTab()
                        .environment(\.router, HomeRouter())
                }
                
                Tab("Settings", systemImage: "gear") {
                    SettingsTab()
                        .environment(\.router, SettingsRouter())
                }
            }
        }
    }
}

Accepted Answer

Each Tab of a TabView defines a separate View hierarchy. (I'm assuming that the root view of each Tab defines its own NavigationStack.) Since an @Environment is supposed to serve data to the child views in its view hierarchy, does this mean that it is possible to define Environments in each tab's root view with the same name (i.e. key) but different values? (I.e., I want a subview to access an environment value for the current view hierarchy without requiring that the subview have any knowledge of which hierarchy it is being called from.) The actual use case has to do with using @Environment in a tabbed application to inject a router in subviews. (Each Tab has its own NavigationStack and its own NavigationPath.)

Environment values are scoped to their view hierarchy. This means that an Environment value set at a tab's root view only affects that tab's subtree.

You can define the same environment key in multiple tabs with different values. Each child views automatically resolve the environment value from their closest enclosing provider in their hierarchy. So a subview in Tab A will get Tab A's environment value, while a subview in Tab B will get Tab B's environment value.

For example:

struct RouterKey: EnvironmentKey {
    static let defaultValue: Router = Router()
}

extension EnvironmentValues {
    var router: Router {
        get { self[RouterKey.self] }
        set { self[RouterKey.self] = newValue }
    }
}

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            TabView {
                Tab("Home", systemImage: "house") {
                    HomeTab()
                        .environment(\.router, HomeRouter())
                }
                
                Tab("Settings", systemImage: "gear") {
                    SettingsTab()
                        .environment(\.router, SettingsRouter())
                }
            }
        }
    }
}

Thank you. That's what I was hoping you'd say.

Cheers

Using @Environment with TabView
 
 
Q