You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带Binding的navigationDestination更新异常:数组与PatientView标题不更新问题

问题:Binding传递至navigationDestination后无法更新视图与数组

我采用接收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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 01:55:35