Let's say I have:
Document
, which represents text document.EditorView
— an NSTextView
, wrapped with Combine, which binds to Document.content<String>
. Document
is a part of complex store:ObservableObject
, so it can be bouneded to EditorView
instance.
When I first create binding, it works as expected — editing NSTextView changes value in Document.content
.
let document1 = Document(...)
let document2 = Document(...)
var editor = EditorView(doc: document1)
But if change binding to another Document...
editor.doc = document2
...then updateNSView
can see new document2
. But inside Coordiantor's textDidChange
has still refrence to document1
.
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.doc.content = textView.string
self.selectedRanges = textView.selectedRanges
}
So, initially, when i set new bindint, NSTextView changes it content to document2
, but as I type, coordinator sends changes to document1
.
Is it true, that Coordiantor keeps it's own copy of parent
, and even if parent changes (@Binding doc
is updated), it still references to old one?
How to make Coordinator reflect parent's bindings changes?
Thank you!
struct Document: Identifiable, Equatable {
let id: UUID = UUID()
var name: String
var content: String
}
struct EditorView: NSViewRepresentable {
@Binding var doc: Document
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeNSView(context: Context) -> CustomTextView {
let textView = CustomTextView(
text: doc.content,
isEditable: isEditable,
font: font
)
textView.delegate = context.coordinator
return textView
}
func updateNSView(_ view: CustomTextView, context: Context) {
view.text = doc.content
view.selectedRanges = context.coordinator.selectedRanges
}
}
// MARK: - Coordinator
extension EditorView {
class Coordinator: NSObject, NSTextViewDelegate {
var parent: EditorView
var selectedRanges: [NSValue] = []
init(_ parent: EditorView) {
self.parent = parent
}
func textDidBeginEditing(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.doc.content = textView.string
self.parent.onEditingChanged()
}
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.doc.content = textView.string
self.selectedRanges = textView.selectedRanges
}
func textDidEndEditing(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.doc.content = textView.string
self.parent.onCommit()
}
}
}
// MARK: - CustomTextView
final class CustomTextView: NSView {
private var isEditable: Bool
private var font: NSFont?
weak var delegate: NSTextViewDelegate?
var text: String {
didSet {
textView.string = text
}
}
// ...
Is it true, that Coordiantor keeps it's own copy of parent, and even if parent changes (@Binding doc is updated), it still references to old one?
A parent, ie EditorView
here, is struct, so answer is yes, in general it can be copied.
How to make Coordinator reflect parent's bindings changes?
Instead of (or additional to) parent, inject binding to Coordinator (via constructor) explicitly and work with it directly.