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
相关产品推荐
相关产品推荐

