You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中为何需重复向Environment注入同一Router对象?

SwiftUI中@Environment注入Router后导航目标视图无法获取的问题

问题重现

我尝试为SwiftUI应用实现一个基于@Environment的集中式Router,核心代码如下:

路由定义

enum Route: String, Hashable {
    case home
    case paywall
    case login
}

Router视图修饰器

struct RouterViewModifier: ViewModifier {
    @State private var router = Router()
    
    @ViewBuilder
    private func routeView(for route: Route) -> some View {
        switch route {
        case .login:
            LoginView()
            
        case .home, .paywall:
            Text(route.rawValue)
        }
    }
    
    func body(content: Content) -> some View {
        NavigationStack(path: $router.path) {
            content
                .environment(router)
                .navigationDestination(for: Route.self) { route in
                    routeView(for: route)
                }
        }
    }
}

extension View {
    func withRouter() -> some View {
        modifier(RouterViewModifier())
    }
}

应用与视图实现

@main
struct SwiftUI_CoordinatorApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
                .withRouter()
        }
    }
}

struct RootView: View {
    @Environment(Router.self) var router: Router?
    
    var body: some View {
        ZStack {
            Text("Splash")
                .onAppear {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                        router?.navigate(to: .login)
                    }
                }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.yellow)
        
    }
}

struct LoginView: View {
    @Environment(Router.self) var router: Router?
    
    var body: some View {
        VStack {
            Button("Login") {
                router?.navigate(to: .home)
            }
        }
        .toolbar(.hidden)
        .padding()
    }
}

问题表现:RootView能正常获取到Router并跳转到LoginView,但进入LoginView后,router为nil,点击按钮无响应。只有在routeView(for:)中给目标视图再次注入router,才能正常工作。

原因分析

这是SwiftUI中NavigationStack的navigationDestination机制导致的:

  • NavigationStack的content参数对应的视图,和navigationDestination中定义的目标视图,属于不同的视图层级。
  • 你在content.environment(router)注入的环境值,只会传递给content及其子视图树,而navigationDestination创建的目标视图,是由导航栈自身管理的独立视图分支,不会自动继承content的环境值。
  • 简单来说,导航目标视图并不是content的子视图,所以无法获取到你在content上设置的环境变量。

解决方案

正如你发现的,需要在navigationDestination返回的目标视图上,再次注入同一个Router实例:

@ViewBuilder
private func routeView(for route: Route) -> some View {
    Group {
        switch route {
        case .login:
            LoginView()
            
        case .home, .paywall:
            Text(route.rawValue)
        }
    }
    .environment(router)
}

这样所有导航目标视图都能获取到同一个Router实例,实现路由控制。

补充说明

如果觉得重复注入麻烦,也可以把navigationDestination的闭包直接写在content的子视图里,但本质上还是需要确保目标视图能拿到环境值。不过当前的方案已经是比较清晰的集中式路由实现方式了。

内容的提问来源于stack exchange,提问作者Shawn Frank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 17:33:17