带Binding的navigationDestination更新异常:数组与PatientView标题不更新问题
我采用接收Binding的ForEach组件,通过navigationDestination将Binding传递至子视图PatientView,但出现更新异常——修改Patient的name后,ContentView的列表及PatientView的navigationTitle都无法同步更新。相关实现代码如下:
import SwiftUI struct PatientView: View { @Binding var patient: Patient var body: some View { Section("Name") { TextField("Name", text: $patient.name) .textFieldStyle(.roundedBorder) } Spacer() .navigationTitle(patient.name) } } protocol BlankInit { init() } extension View { func binding<V>(id: V.ID, array: Binding<[V]>) -> Binding<V> where V: Identifiable { if let ret = array.first(where: { $0.id == id}) { return ret } else { fatalError() } } } extension Binding : Hashable, Equatable where Value: Identifiable, Value: Equatable, Value: Hashable { public static func == (lhs: Binding<Value>, rhs: Binding<Value>) -> Bool { lhs.wrappedValue == rhs.wrappedValue } public func hash(into hasher: inout Hasher) { hasher.combine("Binding") hasher.combine(self.wrappedValue) } } struct Patient: Hashable, Equatable, Identifiable, BlankInit { var id: UUID = UUID() var name: String = "" var age: UInt8 = 0 init(_ name: String, age: UInt8 = 0) { self.name = name self.age = 0 self.id = UUID() } init() { self.name = "" self.age = 0 self.id = UUID() } static func == (lhs: Patient, rhs: Patient) -> Bool { lhs.id == rhs.id && lhs.name == rhs.name && lhs.age == rhs.age } } struct ContentView: View { @StateObject var appData: AppData = AppData() var body: some View { NavigationStack(path: $appData.path) { List { ForEach($appData.patients) { $pt in NavigationLink(pt.name, value: $pt) } } .navigationDestination(for: Binding<Patient>.self) { item in PatientView(patient: item) } .navigationTitle("Patients") } .environmentObject(appData) .padding() } } class AppData: ObservableObject { @Published var path = NavigationPath() @Published var patients = [Patient]() init() { patients.append(Patient("Smith")) patients.append(Patient("Jones")) patients.append(Patient("DeCaro")) } } @main struct TestAppApp: App { var body: some Scene { WindowGroup { ContentView() //.environmentObject(AppData()) } } }
而采用传递Patient对象,再在navigationDestination中创建Binding的实现则可正常运行:
struct ContentView: View { @StateObject var appData: AppData = AppData() var body: some View { NavigationStack(path: $appData.path) { List { ForEach(appData.patients) { pt in NavigationLink(pt.name, value: pt) } } .navigationDestination(for: Patient.self) { item in let binding = binding(id: item.id, array: $appData.patients) PatientView(patient: binding) } .navigationTitle("Patients") } .environmentObject(appData) .padding() } }
我认为Binding版本的实现更简洁优雅,为何无法正常工作?
1. Binding不适合作为NavigationPath的存储元素
SwiftUI的NavigationPath设计用于存储值类型,而Binding本质是状态的引用包装器,并非值类型。当你将Binding<Patient>传入NavigationPath时,路径中存储的是该Binding当前状态的快照,而非持续关联原数据源的引用。后续Patient对象更新时,这个快照式的Binding无法同步反映变化,自然无法触发视图更新。
2. 自定义Binding的Equatable实现加剧状态混乱
你为Binding扩展的Equatable是通过比较wrappedValue来判断相等性。当Patient的name修改时,这个Binding的相等性会发生变化,导致NavigationPath无法稳定识别路径元素,进一步破坏了视图更新的触发逻辑。
3. Binding脱离原绑定上下文
ForEach($appData.patients)生成的Binding是和数组元素直接绑定的,但一旦将这个Binding传入NavigationPath,它就脱离了原数组的绑定上下文,无法再将修改同步回原数组,也无法触发父视图(ContentView的列表)的更新。
传递Patient值类型对象的方案之所以可行,是因为:
- NavigationPath存储的是Patient的稳定值(通过唯一id识别),状态变化可控;
- 在
navigationDestination中通过id重新构建的Binding,是直接关联到appData.patients数组的元素,能实时同步修改到原数据源,并正确触发所有关联视图的更新。
Binding并不适合作为NavigationStack的路径元素,正确的做法始终是传递值类型的模型对象,在目标视图中重新构建与数据源的绑定关系。
内容的提问来源于stack exchange,提问作者mdecaro

