How do we use a custom NSTextContentManager with NSTextParagraph

I've been struggling with this for a while now. To start off with, I am using a custom NSTextLocation as the element range on a NSTextParagraph. NSTextParagraph is a subclass of NSTextElement and both have limited public elements that can be set. From what I can see, the only thing we can set on NSTextParagraph is an NSAttributedString. It has a few other elements it inherits from NSTextElement - primarily textContentManager and elementRange. Seems simple enough...but, there is obviously something wrong with what I am doing.

If we use NSTextStorage as our backing store, then the text ranges it uses is a private type NSCountableTextLocation. Being a private type, I imagine we have to implement our own NSTextLocation to use in the ranges that are set on the NSTextElement.

But, for some reason, when I have multiple text elements, TextKit compares my custom NSTextLocation against a NSCountableTextLocation and crashes.

-[NSCountableTextLocation compare:] receiving unmatching type (3, 1) (3,1) here being the debug description text of my custom text location (represented by line number and column). I am at a complete loss as to why this is being triggered. I have implemented isEqual, Comparable and compare: on my custom implementation.

Going with the following Text

""" First

Second

T """

I create a NSTextParagraph for each paragraph. So, I have elements

  1. 'First\n' - with range (1,1) - (2,1) - length of this range is 6
  2. '\n' - with range (2,1) - (3,1) - length of this range is 1
  3. 'Second\n' with range (3,1) - (4,1) with length being 7
  4. '\n' - range (4,1) - (5,1) with length 1
  5. 'T' - range (5,1) - (5,2) length 1

This bit renders and lays out fine. If I append a character at the last location, I get the exception and crash.

""" First

Second

Th """

Exception being -[NSCountableTextLocation compare:] receiving unmatching type (3, 1)

The number of text elements and the equivalent lengths match what I would get with NSTextStorage (executed in a different playground). There are a few subtle differences I see when debugging with lldb. When I use NSTextStorage, the value of _attributedString on NSTextParagraph of the first text element (and all) is "First\n\nSecond\n\nTh" - essentially the full text. The value of attributedString is "First\n" essentially the range of text that matches the paragraph. The generated text layout fragment has the correct line fragment "First\n" and not the full text. I don't see a public way to setup the NSTextParagraph this way (nor am I able to do it correctly using setValue:forKey either). I don't know if this difference really matters.

So, my questions really are

  1. Does NSTextParagraph support custom NSTextLocation? If so, how do we set it up correctly?
  2. If not, do we subclass NSTextElement and do the layout ourselves?

I have a full minimal project that reproduces this at https://github.com/georgemp/TextLocationCrash but it's a bit of an involved read :-)

I have also filed reports using Feedback Assistant FB13547274

P.S. This crash only seems to occur on Sonoma (not on Monterrey or Ventura)

P.P.S - Thank you for getting this far in this lengthy read :-)