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

SwiftUI中NSViewRepresentable包装的NSTextField不重绘问题

问题描述

实现列表-详情样式视图,详情视图通过NSViewRepresentable包装NSTextField作为文本输入框,出现以下异常:

  1. 选中列表中的人员A;
  2. 切换选中人员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
    }
    
}

问题原因

  1. Coordinator持有旧绑定引用:NSTextFieldRepresentable是值类型,切换选中项时会生成新的实例,但Coordinator初始化时持有了旧实例的拷贝,导致controlTextDidChange方法修改的仍是旧实例的text绑定,对应之前选中的Person对象。
  2. 视图复用导致身份混淆: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 21:06:24