iPad分屏时@Environment变化致NavigationLink自动返回的解决方法
问题原因
当iPad调整窗口大小导致horizontalSizeClass在.compact和.regular间切换时,原代码里的DummyModifier会通过Group切换分支,重新创建整个NavigationView实例。这会直接重置NavigationView的导航栈状态,导致已经push的页面被自动pop回根页面。
解决方案
核心思路是避免因尺寸类别变化重建NavigationView,通过调整弹窗修饰的挂载位置或方式,保持NavigationView实例稳定,从而保留导航栈状态。
方案一:将弹窗修饰移至NavigationView内部
把弹窗的条件判断逻辑放到NavigationView的子视图层级中,而非用修饰器包裹整个NavigationView:
struct ContentView: View { @State var isPresented = false @Environment(\.presentationMode) var presentationMode @Environment(\.horizontalSizeClass) private var horizontalSizeClass var body: some View { NavigationView { VStack { NavigationLink { Button("Dismiss") { presentationMode.wrappedValue.dismiss() } } label: { Text("Click me") } .navigationTitle("Tab 1") } // 弹窗修饰直接挂载到NavigationView的子视图上,不影响NavigationView实例 .applyPopup(isPresented: $isPresented) } .navigationViewStyle(.stack) } } // 自定义扩展,用于挂载弹窗修饰 extension View { func applyPopup(isPresented: Binding<Bool>) -> some View { self.modifier(PopupModifier(isPresented: isPresented)) } } struct PopupModifier: ViewModifier { @Binding var isPresented: Bool @Environment(\.horizontalSizeClass) private var horizontalSizeClass func body(content: Content) -> some View { content .sheet(isPresented: $isPresented) { Text("Sheet") } .fullScreenCover(isPresented: $isPresented) { Text("Fullscreen") } // 尺寸类别变化时,若弹窗已打开则重新适配类型 .onChange(of: horizontalSizeClass) { _ in guard isPresented else { return } isPresented = false DispatchQueue.main.async { isPresented = true } } } }
方案二:保持NavigationView实例稳定的修饰器写法
如果必须用修饰器包裹NavigationView,可通过@ViewBuilder在同一个视图层级上动态控制弹窗,避免切换分支重建NavigationView:
struct ContentView: View { @State var isPresented = false @Environment(\.presentationMode) var presentationMode var body: some View { NavigationView { VStack { NavigationLink { Button("Dismiss") { presentationMode.wrappedValue.dismiss() } } label: { Text("Click me") } .navigationTitle("Tab 1") } } .navigationViewStyle(.stack) .modifier(FixedPopupModifier(isPresented: $isPresented)) } } struct FixedPopupModifier: ViewModifier { @Binding var isPresented: Bool @Environment(\.horizontalSizeClass) private var horizontalSizeClass @ViewBuilder func body(content: Content) -> some View { content .sheet(isPresented: $isPresented) { Text("Sheet") } .fullScreenCover(isPresented: $isPresented) { Text("Fullscreen") } // 尺寸变化时自动适配弹窗类型 .onChange(of: horizontalSizeClass) { _ in guard isPresented else { return } isPresented = false DispatchQueue.main.async { isPresented = true } } } }
关键注意点
- 必须保持
NavigationView实例的稳定性,不能因尺寸类别变化重新创建,否则导航栈状态会丢失。 - 弹窗的条件逻辑应附加在子视图上,或通过动态控制弹窗激活状态实现,而非切换整个父视图层级。
内容的提问来源于stack exchange,提问作者Boyan Pavlov
相关产品推荐
相关产品推荐

