Seemingly incorrect NSTextLineFragments generated with custom subclass to NSTextContentManager

I have a custom subclass to a NSTextContentManager which provides NSTextParagrahs for layout. However, when I have a trailing newline in my content string, the NSTextLayoutFragment does not have a empty NSTextLineFragment indicating a new line. This is in contrast to using the standard NSTextContentStorage. NSTextContentStorage also uses NSTextParagraphs to represent it's text elements.

The code I have for both scenarios are

  1. Using Default NSTextContentStorage
let layoutManager = NSTextLayoutManager()
let container = NSTextContainer(size: NSSize(width: 400, height: 400))
layoutManager.textContainer = container
let contentStorage = NSTextContentStorage()
contentStorage.textStorage?.replaceCharacters(in: NSRange(location: 0, length: 0), with: "It was the best of times.\n")
contentStorage.addTextLayoutManager(layoutManager)
layoutManager.enumerateTextLayoutFragments(from: contentStorage.documentRange.location, options: .ensuresLayout) { textLayoutFragment in
    print("defaultTextLineFragments:")
    for (index, textLineFragment) in textLayoutFragment.textLineFragments.enumerated() {
        print("\(index): \(textLineFragment)")
    }

    print("\n")
    return true
}

This outputs

defaultTextLineFragments:
0: <NSTextLineFragment: 0x123815a80 "It was the best of times.
">
1: <NSTextLineFragment: 0x123825b00 "">
  1. Using custom subclass to NSTextContentManager
class CustomTextLocation: NSObject, NSTextLocation {

    let offset: Int

    init(offset: Int) {
        self.offset = offset
    }

    func compare(_ location: NSTextLocation) -> ComparisonResult {
        guard let location = location as? CustomTextLocation else {
            return .orderedAscending
        }

        if offset < location.offset {
            return .orderedAscending
        } else if offset > location.offset {
            return .orderedDescending
        } else {
            return .orderedSame
        }
    }
}

class CustomStorage: NSTextContentManager {

    let content = "It was the best of times.\n"

    override var documentRange: NSTextRange {
        NSTextRange(location: CustomTextLocation(offset: 0), end: CustomTextLocation(offset: content.utf8.count))!
    }

    override func textElements(for range: NSTextRange) -> [NSTextElement] {
        let paragraph = NSTextParagraph(attributedString: NSAttributedString(string: content))
        paragraph.textContentManager = self
        paragraph.elementRange = documentRange

        return [paragraph]
    }

    override func enumerateTextElements(from textLocation: NSTextLocation?, options: NSTextContentManager.EnumerationOptions = [], using block: (NSTextElement) -> Bool) -> NSTextLocation? {
        // Just assuming static text elements for this example
        let elements = self.textElements(for: documentRange)
        for element in elements {
            block(element)
        }

        return elements.last?.elementRange?.endLocation
    }

    override func location(_ location: NSTextLocation, offsetBy offset: Int) -> NSTextLocation? {
        guard let location = location as? CustomTextLocation,
              let documentEnd = documentRange.endLocation as? CustomTextLocation else {
            return nil
        }

        let offset = CustomTextLocation(offset: location.offset + offset)
        if offset.compare(documentEnd) == .orderedDescending {
            return nil
        }

        return offset
    }

    override func offset(from: NSTextLocation, to: NSTextLocation) -> Int {
        guard let from = from as? CustomTextLocation,
              let to = to as? CustomTextLocation else {
            return 0
        }

        return to.offset - from.offset
    }

}

let customLayoutManager = NSTextLayoutManager()
let customContainer = NSTextContainer(size: NSSize(width: 400, height: 400))
customLayoutManager.textContainer = customContainer
let customStorage = CustomStorage()
customStorage.addTextLayoutManager(customLayoutManager)

customLayoutManager.enumerateTextLayoutFragments(from: customStorage.documentRange.location, options: .ensuresLayout) { textLayoutFragment in
    print("customStorage textLineFragments:")

    for (index, textLineFragment) in textLayoutFragment.textLineFragments.enumerated() {
        print("\(index): \(textLineFragment)")
    }

    print("\n")
    return true
}

This output

customStorage textLineFragments:
0: <NSTextLineFragment: 0x13ff0c8d0 "It was the best of times.
">

I am expecting the two outputs to match (as it's impacting my position calculations for carets during text entry). How would I go about getting the text layout manager to add the extra line fragment while using a custom NSTextContentManager

Seemingly incorrect NSTextLineFragments generated with custom subclass to NSTextContentManager
 
 
Q