SwiftUI列表重排序后绑定NSViewRepresentable的TextField编辑错误问题
问题原因
- Coordinator 持有了初始化时的旧
GrowingField实例
你在 Coordinator 中用let parent: GrowingField存储了初始化时传入的实例,这个值一旦生成就不会再更新。列表重排序后,同位置的GrowingField的text绑定、submit闭包已经指向了新的列表项,但 Coordinator 里的parent还是初始化时的旧实例,所以触发回调时会对应到重排序前的逻辑,造成绑定错位。 - 视图复用导致状态残留
默认情况下ForEach会复用同位置的视图实例,重排序后同位置的NSTextField不会重新创建,只会调用updateNSView刷新,你之前的代码只更新了textBinding,没有同步最新的submit回调,就会出现状态残留。
修复方案
第一步:修改 GrowingField 逻辑,移除 Coordinator 对旧实例的持有
不要在 Coordinator 中存储固定的 GrowingField 实例,每次调用 updateNSView 时同步最新的绑定和回调:
struct GrowingField: NSViewRepresentable{ @Binding var text: String var submit:(() -> Void)? //Hit enter func makeNSView(context: Context) -> NSTextField { let textField = NSTextField() textField.delegate = context.coordinator textField.stringValue = text return textField } func updateNSView(_ nsView: NSTextField, context: Context) { nsView.stringValue = text // 同步最新的绑定和回调 context.coordinator.textBinding = $text context.coordinator.currentSubmit = submit } func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator: NSObject, NSTextFieldDelegate { var textBinding : Binding<String>? var currentSubmit: (() -> Void)? func controlTextDidChange(_ obj: Notification) { guard let textField = obj.object as? NSTextField else { return } self.textBinding?.wrappedValue = textField.stringValue } func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { switch commandSelector{ case #selector(NSStandardKeyBindingResponding.insertNewline(_:)): currentSubmit?() textView.window?.makeFirstResponder(nil) return true default: return false } } } }
第二步:给列表项添加唯一标识(可选优化)
给 ChildView 加上基于数据 ID 的标识,确保重排序后数据变化时会重新创建视图,避免不必要的复用带来的状态问题:
ForEach($model.things){ $thing in ChildView(thing: $thing) .id(thing.id) .onDrag{ model.draggedThing = thing return NSItemProvider(object: NSString()) } }
内容的提问来源于stack exchange,提问作者Clifton Labrum
相关产品推荐
相关产品推荐

