I created a list-detail view like this. In the detail view, there is some input field that accepts text. The input field is a NSTextField
wrapped using NSViewRepresentable
. The issue happens in the below scenario.
First, click a row and select person A. Then try to edit another person's properties by selecting a newer person B. But here the update happens of the first person's properties (A's).
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
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
}
}
Here
self.textField
is a copy of view representable, but on binding changed theNSTextFieldRepresentable
is not re-created - onlyupdateNSView
is called to refresh native part, so coordinator still have value copied on creation. That's the issue.A possible fix in this design is to update coordinator on binding change as well:
Tested with Xcode 13.4 / macOS 12.4