SwiftUI NavigationLink深层跳转选中状态丢失原因及解决方法
问题代码
enum SelectionItem:Int { case first case second } class ContentViewModel:ObservableObject { @Published var selectedItem:SelectionItem? } struct MyCar { let name:String } struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @StateObject var model = ContentViewModel() var body: some View { NavigationView { Form { Section { NavigationLink(destination: DetailsView(car: MyCar(name: "FIRST")) , tag: .first, selection: $model.selectedItem) { Text("FIRST") } } Section { NavigationLink(destination: DetailsView(car: MyCar(name: "SECOND")) , tag: .second, selection: $model.selectedItem) { Text("SECOND") } } Section { switch model.selectedItem { case .first: Text("CURRENT SELECTION FIRST") .foregroundColor(.green) case .second: Text("CURRENT SELECTION SECOND") .foregroundColor(.green) default: Text("NO SELECTION") .foregroundColor(.red) } } } Text("Select an item") } .onChange(of: model.selectedItem) { newValue in print("new selection: \(newValue)") } } } struct DetailsView: View { let car:MyCar var body: some View { VStack { Text(car.name) NavigationLink { DetailsView(car: car) } label: { Text("GO") } } } }
异常现象
点击Form列表中的FIRST或SECOND导航项进入DetailsView页面后,若点击DetailsView内的「GO」按钮进一步跳转到下一级页面,ViewModel中被@Published修饰的selectedItem属性会被自动设置为nil,左侧Form列表中对应导航项的选中高亮效果也会同步消失,视觉选中状态完全丢失。
问题产生原因
你使用的NavigationLink(tag:selection:)初始化方法自带双向同步逻辑:系统会自动将当前导航栈的栈顶页面和绑定的selection值做强制同步。
- 点击列表项跳转到一级
DetailsView时,栈顶和当前NavigationLink的目标页面匹配,系统会把selectedItem设为对应tag值 - 从一级详情页push二级页面后,栈顶不再是列表中NavigationLink直接绑定的一级页面,系统判定当前无匹配tag的选中项,自动将
selectedItem回写为nil,最终导致选中高亮消失。
这是老版NavigationView的默认行为,不是业务代码逻辑错误。
修复方案
根据项目最低支持iOS版本二选一即可:
方案1:兼容iOS15及更低版本
将选中状态存储和导航激活状态解耦,不要完全依赖系统自动管理selection值:
- 单独存储每个导航项的激活状态,只有用户主动点击列表项、或从一级页面pop回根列表时才修改
selectedItem - 忽略系统在二级push时自动回写的
nil值
核心修改代码:
class ContentViewModel:ObservableObject { @Published var selectedItem:SelectionItem? // 单独控制两个导航项的激活状态 @Published var isFirstActive = false @Published var isSecondActive = false } // 替换原有的selection绑定式NavigationLink Section { NavigationLink( destination: DetailsView(car: MyCar(name: "FIRST")), isActive: $model.isFirstActive ) { Text("FIRST") } .onChange(of: model.isFirstActive) { isActive in if isActive { model.selectedItem = .first } else { // 仅从一级页面返回根列表时清空选中 model.selectedItem = nil } } } Section { NavigationLink( destination: DetailsView(car: MyCar(name: "SECOND")), isActive: $model.isSecondActive ) { Text("SECOND") } .onChange(of: model.isSecondActive) { isActive in if isActive { model.selectedItem = .second } else { model.selectedItem = nil } } }
修改后push二级页面时不会触发对应导航项的激活状态变更,selectedItem会保留原有值,列表高亮不会消失。
方案2:iOS16+ 推荐使用新导航栈API
如果项目最低支持iOS16,直接废弃老NavigationView,改用NavigationStack自行持有导航路径,选中状态完全由业务控制,不会出现系统自动回写值的问题:
class ContentViewModel:ObservableObject { @Published var selectedItem:SelectionItem? // 自行维护导航路径 @Published var navPath = NavigationPath() } struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @StateObject var model = ContentViewModel() var body: some View { NavigationStack(path: $model.navPath) { Form { Section { Button { model.selectedItem = .first model.navPath.append(MyCar(name: "FIRST")) } label: { HStack { Text("FIRST") Spacer() if model.selectedItem == .first { Image(systemName: "checkmark") } } .foregroundColor(.primary) } } Section { Button { model.selectedItem = .second model.navPath.append(MyCar(name: "SECOND")) } label: { HStack { Text("SECOND") Spacer() if model.selectedItem == .second { Image(systemName: "checkmark") } } .foregroundColor(.primary) } } Section { switch model.selectedItem { case .first: Text("CURRENT SELECTION FIRST") .foregroundColor(.green) case .second: Text("CURRENT SELECTION SECOND") .foregroundColor(.green) default: Text("NO SELECTION") .foregroundColor(.red) } } } .navigationDestination(for: MyCar.self) { car in DetailsView(car: car) } Text("Select an item") } .onChange(of: model.navPath) { newPath in // 回到根页面时清空选中 if newPath.isEmpty { model.selectedItem = nil } } } }
新API下导航路径完全由业务持有,不管push多少级子页面,列表选中状态都会一直保留,直到手动pop回根列表。
内容的提问来源于stack exchange,提问作者zumzum
相关产品推荐
相关产品推荐

