问题
I have NSTextViews where I need to track the current end point of the text to place interface elements.
I bind a model string to NSBindingName.value on the text view.
When the text is edited I update the location of my interface elements in
func textDidChange(_ notification: Notification)
...as a delegate to the NSTextView.
However, if my model is the source of a string update, this delegate method is never called, even though the text is correctly updated in the NSTextView.
So, am I doing something wrong and the bound change should call textDidChange?
If not, and this is a bug or by design, should I just be observing and manually updating values and calling by own delegate method? It seems a shame to lose the elegance of the binding.
Please note that this has been asked before, but marked as correctly answered when it is not: NSTextView textDidChange/didChangeText not called for bindings
回答1:
The NSTextView method didChangeText is not called when a binding updates the text, rather than the text view updating the model.
didChangeText is the source of the binding update. If you override it and don't call super, the binding is broken. didChangeText calls the delegate method textDidChange.
Unfortunately, didChangeText is also called rather late in the NSTextView update process - after the layout and storage delegate calls.
This scuppered me because I needed the model to change before I called my delegate - I was kicking off separate calculations for NSTableView row heights with another view. If these were done before the model update I got the wrong heights.
I found no way of differentiating between model and textview updates to the string from within the NSTextView code. I caught all updates to the string in didProcessEditing as an NSTextStorage delegate.
func textStorage(_ textStorage: NSTextStorage,
didProcessEditing editedMask: NSTextStorageEditActions,
range editedRange: NSRange,
changeInLength delta: Int)
{
if editedMask.contains(.editedCharacters) {
textStringDidChange = true
}
}
I just set a flag here, because calls to my delegate here would crash if they tried to update the NSTextView in any way. I then used this flag in a following NSLayoutManagerDelegate call:
func layoutManagerDidInvalidateLayout(_ sender: NSLayoutManager)
{
if textStringDidChange {
delegate?.textDidChange?(Notification(name: .init("")))
textStringDidChange = false
}
}
didChangeText is called sometime after these delegate calls. Therefore I had to make do with having text-view initialised changes calling my delegate twice. It is inefficient but I found no way of filtering that out.
来源:https://stackoverflow.com/questions/54574055/nstextview-textdidchange-not-called-through-binding