TextKit 2: edits make duplicate text elements

I'm having an issue with a TextKit2 NSTextView (AppKit). In my subclass's keyDown, I'm doing a bit of manipulation of the textStorage but it seems that if I try to alter any part of the textStorage where a text paragraph and accompanying fragment have already been created, I then get duplicate paragraphs and fragments.

For example, say I want to insert new text at the end of an existing paragraph. I've tried:

  • Inserting a new attr string (with attrs matching the preceding text) at the end of the existing paragraph text
  • Replacing the existing paragraph text with a new attr string that includes the new text
  • First deleting the existing paragraph text and then inserting the new full attr string including the new text

I am wrapping those edits in a textContentStorage.performEditingTransaction closure.

But in all cases, it seems that TextKit 2 wants to create new NSTextParagraph and NSTextFragment objects and doesn't remove the old ones, resulting in duplicate elements in my UI.

Some sample code:

let editLocation = editRange.location
guard editLocation > 0 else {
    break
}
let attrs = textStorage.attributes(at: editLocation, effectiveRange: nil)

// paragraphStartLocation is in an NSAttributedString extension not shown
guard let paragraphStartLoc = textStorage.paragraphStartLocation(from: editLocation) else {
    assertionFailure(); return
}

var paragraphRange = NSRange(location: paragraphStartLoc, length: editLocation - paragraphStartLoc + 1)

var fullParagraph = textStorage.attributedSubstring(from: paragraphRange).string

fullParagraph += newText

let newAttrStr = NSAttributedString(string: fullParagraph, attributes: attrs)

textContentStorage.performEditingTransaction {
    textStorage.deleteCharacters(in: paragraphRange)
    textStorage.insert(newAttrStr, at: paragraphStartLoc)
}

I think the problem has to do with what's being requested from NSTextContentStorageDelegate.textContentStorage(range). Even though in the performEditingTransaction block I deleted the entire paragraph range and then re-inserted it with the replacement string and longer length, the delegate is getting called twice with two ranges: a range covering the original length before the edit and a smaller range covering only what was added during the edit. But my delegate is returning a full paragraph object for both, resulting in the duplicates. I need the delegate to only get called for the new aggregate range.

TextKit 2: edits make duplicate text elements
 
 
Q