SwiftUI中NavigationView页面push后立即自动pop问题如何修复?
问题根因
NSArray属于Objective-C引用类型,SwiftUI判断NavigationLink的目标视图是否变更时,对引用类型会比对内存地址而非内容。当前代码每次构建Destination时都会重新生成新的NSArray实例,即使内容完全相同,地址不同也会被判定为目标视图发生了变更。GeometryReader的闭包会在自身布局参数变化时重新执行,代码中使用geom.safeAreaInsets.top构建顶部Spacer,当NavigationLink触发push操作时,当前页面的安全区域Insets会发生变化,触发GeometryReader整体重绘,所有NavigationLink的destination都会被重新构建。已经激活的NavigationLink因为目标视图被判定为变更直接失效,系统就会自动触发pop操作退回上一页。- 你的测试现象也可以对应验证:参数换成值类型
Int后,SwiftUI会比对值而非地址,重绘后相同值会被判定为目标视图未变更,因此不会触发pop;注释掉用到安全区Insets的Spacer后,GeometryReader不会因为push时的安全区变化触发重绘,NavigationLink的目标视图不会被重新构建,也不会触发问题。
修复方案
- 方案1:替换引用类型参数为Swift原生值类型
把Destination接收的NSArray类型参数替换为Swift原生的Array<Int>,值类型的比对规则为内容比对,重绘时生成的同内容数组不会被判定为目标变更,示例修改:
struct Destination : View { private let some: [Int] // 替换为Swift原生数组 init(some: [Int]) { self.some = some } var body: some View { Text("e") } } // 调用时直接传Swift数组即可 destination: Destination(some: [34, 53, 45, 34566]).navigationBarHidden(true)
- 方案2:提前创建引用类型实例
如果必须使用NSArray,可以在RecordingsView中提前初始化好需要用到的NSArray实例,避免重绘时重复创建新对象:
struct RecordingsView : View { // 提前创建固定的NSArray实例,重绘时不会变更 private let list1 = NSArray(array: [34, 53, 45, 34566]) private let list2 = NSArray(array: [34, 53, 45]) var body: some View { // 其余代码不变,调用时传提前创建的实例即可 destination: Destination(some: list1).navigationBarHidden(true) } }
- 方案3:优化GeometryReader布局结构
你当前的GeometryReader中直接放置了两个平级的VStack,会导致布局计算不稳定,建议用ZStack包裹背景层和内容层,减少不必要的重绘:
GeometryReader { geom in ZStack { // 新增ZStack包裹 // 背景层放下层 VStack { Spacer().frame(maxWidth: .infinity, maxHeight: geom.safeAreaInsets.top).background(Color.red) Spacer().frame(maxWidth: .infinity).background(Color.white) }.edgesIgnoringSafeArea([.top, .bottom]) // 内容层放上层 VStack(spacing: 0) { // 原有内容不变 } } }
内容的提问来源于stack exchange,提问作者Semyon Tikhonenko
相关产品推荐
相关产品推荐

