SwiftUI导航异常求助:点击跳转编辑项后返回初始视图
SwiftUI三层导航栈异常返回问题排查与修复
问题原因
- Hashable实现错误:
InnerDataItem的==和hash方法依赖可变的innerInfo属性,当在编辑页修改innerInfo时,对象的哈希值和相等性会发生变化。SwiftUI的NavigationLink(value:)依赖对象的稳定标识管理导航栈,标识突变会导致导航栈无法正确识别当前页面,触发异常返回。 - 导航目的地嵌套定义:
DataList中嵌套定义navigationDestination,虽不是直接诱因,但不符合SwiftUI导航最佳实践,可能引发导航栈状态管理混乱。
修复方案
- 修正Hashable实现:改用不可变的
id属性作为InnerDataItem的相等性和哈希判断依据,保证对象标识的稳定性。 - 统一导航目的地位置:将所有
navigationDestination移至顶层NavigationStack下,让导航栈统一管理所有页面跳转逻辑。
修改后的完整代码
import SwiftUI @Observable class InnerDataItem : Hashable, Identifiable { let id = UUID() // 改为不可变常量 var innerInfo = "" init(innerInfo: String = "") { self.innerInfo = innerInfo } static func == (lhs: InnerDataItem, rhs: InnerDataItem) -> Bool { lhs.id == rhs.id // 用唯一ID判断相等性 } func hash(into hasher: inout Hasher) { hasher.combine(id) // 用唯一ID生成哈希值 } } @Observable class DataModel : Identifiable { let id: UUID = UUID() var info = "My String array" var innerData: [InnerDataItem] = [] init(info: String = "My String array", innerData: [InnerDataItem]) { self.info = info self.innerData = innerData } } let testData = DataModel( info: "Data model object", innerData: [ InnerDataItem(innerInfo: "(1) Inner info One"), InnerDataItem(innerInfo: "(1) Inner info Two"), InnerDataItem(innerInfo: "(1) Inner info Three"), ]) struct DataListView: View { var data: DataModel var body: some View { DataList(data: data) } } struct DataList: View { var data: DataModel var body: some View { List { ForEach(data.innerData) { item in NavigationLink(value: item) { DataLine(dataItem: item) } } } } } struct DataLine: View { var dataItem: InnerDataItem var body: some View { VStack(alignment: .leading) { Text(dataItem.innerInfo).font(.largeTitle) } } } struct DataEditItem: View { @Bindable var dataItem: InnerDataItem var body: some View { Form { Section(header: Text("显示名称")) { TextField("", text: $dataItem.innerInfo, prompt: Text("输入内容")) } } } } struct ContentView: View { @State private var gotoDataList = false var body: some View { NavigationStack { List { Button("进入数据列表") { gotoDataList.toggle() } } .navigationDestination(isPresented: $gotoDataList) { DataListView(data: testData) } // 将InnerDataItem的导航目的地统一放在顶层 .navigationDestination(for: InnerDataItem.self) { item in DataEditItem(dataItem: item) } } } } @main struct ListNavTestAppApp: App { var body: some Scene { WindowGroup { ContentView() } } } #Preview { ContentView() }
额外说明
- 对于可编辑的Observable对象,禁止用可变属性作为Hashable的判断依据,必须依赖稳定的唯一标识(如UUID)。
- 所有导航目的地尽量统一放在顶层NavigationStack下,避免嵌套定义导致的导航状态异常。
内容的提问来源于stack exchange,提问作者Per Lindgren
相关产品推荐
相关产品推荐

