I have a custom subclass to a NSTextContentManager
which provides NSTextParagrah
s 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 NSTextParagraph
s to represent it's text elements.
The code I have for both scenarios are
- 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 "">
- 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