SwiftUI问题:列表更新时NavigationView详情页自动返回栈
问题原因
当列表重新排序后,SwiftUI的List会重新渲染列表项,原NavigationLink对应的列表项在新排序中位置发生变化,系统会误判该导航链接对应的视图已被移除,从而自动弹出导航栈。另外,IdAndScoreItem作为引用类型,@State数组的更新方式会触发不必要的视图刷新,进一步加剧这个问题。
解决方案
方案1:将IdAndScoreItem改为结构体(推荐,iOS 16+)
结构体是值类型,@State可以更精准地跟踪数据变化,避免引用类型带来的意外问题,同时让列表刷新更可控,不会导致NavigationLink关联失效。
修改后的完整代码:
import SwiftUI struct IdAndScoreItem: Identifiable { let id: Int var score: Int } @main struct CrazyBackStackProblemApp: App { var body: some Scene { WindowGroup { NavigationStack { ListView() } } } } struct ListView: View { @State var items = (1...50).map { IdAndScoreItem(id: $0, score: 0) } func addScoreAndSort(item: IdAndScoreItem) { items = items.map { currentItem in guard currentItem.id == item.id else { return currentItem } var updatedItem = currentItem updatedItem.score += 1 return updatedItem } .sorted { $0.score > $1.score } } var body: some View { List(items) { item in NavigationLink { ScoreClickerView(onClick: { addScoreAndSort(item: item) }) } label: { Text("id: \(item.id) score:\(item.score)") } } } } struct ScoreClickerView: View { var onClick: () -> Void var body: some View { Text("tap me to increase the score") .onTapGesture { onClick() } } }
方案2:手动控制导航状态(兼容iOS 15及以下)
如果需要兼容iOS 15,可使用NavigationView结合NavigationLink(isActive:)手动控制导航激活状态,避免列表排序变化导致导航栈自动弹出。
修改后的完整代码:
import SwiftUI struct IdAndScoreItem: Identifiable { let id: Int var score: Int } @main struct CrazyBackStackProblemApp: App { var body: some Scene { WindowGroup { NavigationView { ListView() } .navigationViewStyle(.stack) } } } struct ListView: View { @State var items = (1...50).map { IdAndScoreItem(id: $0, score: 0) } @State private var selectedItemId: Int? = nil func addScoreAndSort(itemId: Int) { items = items.map { currentItem in guard currentItem.id == itemId else { return currentItem } var updatedItem = currentItem updatedItem.score += 1 return updatedItem } .sorted { $0.score > $1.score } } var body: some View { List(items) { item in NavigationLink( destination: ScoreClickerView { addScoreAndSort(itemId: item.id) }, isActive: Binding( get: { selectedItemId == item.id }, set: { isActive in if isActive { selectedItemId = item.id } else { selectedItemId = nil } } ) ) { Text("id: \(item.id) score:\(item.score)") } } } } struct ScoreClickerView: View { var onClick: () -> Void var body: some View { Text("tap me to increase the score") .onTapGesture { onClick() } } }
关键说明
- 结构体替代类:值类型的结构体让
@State能准确跟踪每个item的变化,避免引用类型带来的意外数据共享问题,列表刷新更稳定。 - NavigationStack替代NavigationView:iOS 16+推出的
NavigationStack比旧的NavigationView更稳定,对列表项变化的处理更友好。 - 手动控制导航状态:通过
isActive绑定管理导航激活状态,即使列表排序变化,只要selectedItemId保持正确,导航栈就不会意外弹出。
内容的提问来源于stack exchange,提问作者mmm111mmm
相关产品推荐
相关产品推荐

