SwiftUI中NSViewRepresentable包装的NSTextField不重绘问题
问题描述
实现列表-详情样式视图,详情视图通过NSViewRepresentable包装NSTextField作为文本输入框,出现以下异常:
- 选中列表中的人员A;
- 切换选中人员B并尝试编辑其属性;
此时实际修改的仍是人员A的属性。
相关代码
ContentView
struct ContentView: View { @State var selectedItem: Person? var body: some View { HStack { List(selection: $selectedItem) { ForEach(modelData, id: \.self) { person in Text(person.firstName) } } .border(.separator, width: 1) .listStyle(.plain) .padding() .frame(width: 200) VStack { if let selectedItem = selectedItem { InformationView(person: PersonModel(person: selectedItem)) } else { Text("No Item Selected").foregroundColor(Color(NSColor.tertiaryLabelColor)) } } .frame(width: 300) .padding() } } }
InformationView
struct InformationView: View { @ObservedObject var person: PersonModel var body: some View { Form { NSTextFieldRepresentable(placeholder: "", text: $person.person.firstName) NSTextFieldRepresentable(placeholder: "", text: $person.person.secondName) NSTextFieldRepresentable(placeholder: "", text: $person.person.city) } } }
NSTextFieldRepresentable
struct NSTextFieldRepresentable: NSViewRepresentable { let placeholder: String @Binding var text: String func makeNSView(context: Context) -> NSTextField { let view = NSTextField() view.delegate = context.coordinator return view } func updateNSView(_ nsView: NSTextField, context: Context) { nsView.stringValue = text nsView.placeholderString = NSLocalizedString(placeholder, comment: "") } func makeCoordinator() -> NSTextFieldCoordinator { NSTextFieldCoordinator(textField: self) } class NSTextFieldCoordinator: NSObject, NSTextFieldDelegate { var textField: NSTextFieldRepresentable init(textField: NSTextFieldRepresentable) { self.textField = textField } func controlTextDidChange(_ obj: Notification) { textField.text = (obj.object as? NSTextField)?.stringValue ?? "" } } }
Model类
class Person: NSObject, Identifiable { var id = UUID() var firstName: String var secondName: String var city: String init (firstName: String, secondName: String, city: String) { self.firstName = firstName self.secondName = secondName self.city = city } } var modelData: [Person] = [ Person(firstName: "Lionel", secondName: "Messi", city: "Paris"), Person(firstName: "Cristiano", secondName: "Ronaldo", city: "Manchester"), Person(firstName: "Sachin", secondName: "Tendulkar", city: "Mumbai"), Person(firstName: "Virat", secondName: "Kohli", city: "Delhi") ] class PersonModel: ObservableObject { @Published var person: Person init(person: Person) { self.person = person } }
问题原因
- Coordinator持有旧绑定引用:
NSTextFieldRepresentable是值类型,切换选中项时会生成新的实例,但Coordinator初始化时持有了旧实例的拷贝,导致controlTextDidChange方法修改的仍是旧实例的text绑定,对应之前选中的Person对象。 - 视图复用导致身份混淆:SwiftUI无法区分不同选中项对应的
NSTextFieldRepresentable实例,会复用原有视图及其Coordinator,绑定未同步切换到新的Person对象。
解决方案
方案1:修复Coordinator的绑定逻辑
让Coordinator直接持有Binding<String>而非整个NSTextFieldRepresentable实例,确保绑定随视图更新同步:
struct NSTextFieldRepresentable: NSViewRepresentable { let placeholder: String @Binding var text: String func makeNSView(context: Context) -> NSTextField { let view = NSTextField() view.delegate = context.coordinator return view } func updateNSView(_ nsView: NSTextField, context: Context) { // 避免编辑时被视图更新打断 if nsView.stringValue != text { nsView.stringValue = text } nsView.placeholderString = NSLocalizedString(placeholder, comment: "") // 更新Coordinator的绑定 context.coordinator.text = $text } func makeCoordinator() -> NSTextFieldCoordinator { NSTextFieldCoordinator(text: $text) } class NSTextFieldCoordinator: NSObject, NSTextFieldDelegate { @Binding var text: String init(text: Binding<String>) { self._text = text } func controlTextDidChange(_ obj: Notification) { guard let textField = obj.object as? NSTextField else { return } text = textField.stringValue } } }
方案2:给视图添加唯一标识
通过id修饰符让SwiftUI区分不同选中项对应的视图,强制生成新的视图实例和Coordinator:
修改ContentView中InformationView的调用:
InformationView(person: PersonModel(person: selectedItem)) .id(selectedItem.id)
额外优化:增强Model的响应性
当前Person类修改属性不会触发SwiftUI更新,可将其改为ObservableObject并使用@Published标记属性:
class Person: NSObject, Identifiable, ObservableObject { var id = UUID() @Published var firstName: String @Published var secondName: String @Published var city: String init(firstName: String, secondName: String, city: String) { self.firstName = firstName self.secondName = secondName self.city = city } }
内容的提问来源于stack exchange,提问作者Sooraj P
相关产品推荐
相关产品推荐

