Michael Berk
Michael Berk

Reputation: 465

Array Binding in NSViewRepresentable Updating Incorrectly After Sorting

I have a ForEach binding to an array of items. Each item has an index (which represents how it should be sorted), and some text. Each row in the ForEach has an NSTextView in an NSViewRepresentable (I can't use TextEditor for various reasons), which uses the binding for the item's text. However, after sorting the list, if I update the NSTextView, one of the other views gets the update as well:

Updating NSTextView after sorting items in list causes another to update

Here's my code:

struct ListItem: Hashable, Identifiable {
    
    var index: Int
    var text: String
    var id = UUID()
    
    static let initialItems: [ListItem] = [
        .init(index: 2, text: "String 2"),
        .init(index: 0, text: "String 0"),
        .init(index: 1, text: "String 1"),
    ]
}

struct ContentView: View {
    
    @State var items = ListItem.initialItems
    
    var body: some View {
        Form {
            ForEach($items) { item in
                HStack {
                    TextField("Index: ", value: item.index, format: .number)
                        .textFieldStyle(.roundedBorder)
                    TVRep(text: item.text)
                }
            }
            Button("Sort!")  {
                items.sort(by: {$0.index < $1.index})
            }
        }
        .padding()
    }
    
}

struct TVRep: NSViewRepresentable {
    @Binding var text: String
    func makeNSView(context: Context) -> NSTextView {
        let view = NSTextView()
        view.string = text
        view.delegate = context.coordinator
        return view
    }
    
    func updateNSView(_ nsView: NSTextView, context: Context) {
        nsView.string = text
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, NSTextViewDelegate {
        var parent: TVRep!
        
        init(parent: TVRep) {
            super.init()
            self.parent = parent
        }
        
        func textDidChange(_ notification: Notification) {
            guard let tv = notification.object as? NSTextView else {return}
            parent.text = tv.string
        }
        
    }
}

This doesn't occur when using SwiftUI's built-in TextEditor. Interestingly, this only seems to happen when I update the text binding's value within the Representable view - if I don't have the delegate method updating the binding value, this doesn't occur.

I do know that if I call sorted(by:) in the ForEach argument that it will update properly, but that means that the moment that the TextField for the index is updated, the views are sorted, while I want to be able to sort them manually.

Upvotes: 0

Views: 176

Answers (0)

Related Questions