NSTextElementProvider has a function
optional func offset(
from: NSTextLocation,
to: NSTextLocation
) -> Int
The to param here isn't an optional. NSTextLocation is a protocol. So, I was expecting it to require an instance of an object implementing the protocol. But, I seem to be getting passed what seems to be a null (from AppKit) (BuiltIn.RawPointer) 0x0.
Why is the compiler allowing this (or is that allowed)?
To guard against this, I'm now doing something like this
var end: MarkupText.Location
if let to = to as? MarkupText.Location {
end = to
} else {
end = range.upperBound
}
Is there a better way to do this (or avoid it)? Thanks
Edit:
Sample Code to Reproduce (also available at https://github.com/georgemp/TextLocationCrash). I'm using a custom NSTextLocation, and NSTextLayoutManager. The crash seems to occur when setting a NSTextContainer on the NSTextLayoutManager (if I don't set the text container, the code does not crash).
//
// ViewController.swift
// TextLocationCrash
//
// Created by George Philip Malayil on 06/10/23.
//
import Cocoa
class ViewController: NSViewController {
var layoutManager: NSTextLayoutManager!
var textContainer: NSTextContainer!
var documentModel: DocumentModel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let textContainer = NSTextContainer(size: NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
self.textContainer = textContainer
let layoutManager = NSTextLayoutManager()
layoutManager.textContainer = textContainer
self.layoutManager = layoutManager
self.documentModel = DocumentModel()
documentModel.attach(textLayoutManager: layoutManager)
layoutManager.enumerateTextLayoutFragments(from: CustomTextLocation(column: 1), options: [.reverse, .ensuresLayout]) { fragment in
print("\(fragment)")
return false
}
}
}
//
// Document.swift
// TextLocationCrash
//
// Created by George Philip Malayil on 06/10/23.
//
import AppKit
class CustomTextLocation: NSObject {
var column: Int = 1 // Not zero indexed.
init(column: Int) {
self.column = column
}
override public var description: String {
"\(column)"
}
}
extension CustomTextLocation: NSTextLocation {
func compare(_ location: NSTextLocation) -> ComparisonResult {
guard let location = location as? CustomTextLocation else {
fatalError("Expected Document.Location")
}
if column < location.column {
return .orderedAscending
} else if column == location.column {
return .orderedSame
} else {
return .orderedDescending
}
}
}
//
// DocumentModel.swift
// TextLocationCrash
//
// Created by George Philip Malayil on 06/10/23.
//
import AppKit
class DocumentModel: NSTextContentManager {
var layoutManager: NSTextLayoutManager!
var data = ""
override var documentRange: NSTextRange {
NSTextRange(location: CustomTextLocation(column: 1), end: CustomTextLocation(column: 1))!
}
func attach(textLayoutManager: NSTextLayoutManager) {
self.layoutManager = textLayoutManager
super.addTextLayoutManager(layoutManager)
}
// MARK: NSTextContentManager overrides
override func textElements(for range: NSTextRange) -> [NSTextElement] {
[NSTextParagraph(textContentManager: self)]
}
// MARK: NSTextElementProvider Overrides
override func enumerateTextElements(from textLocation: NSTextLocation?, options: NSTextContentManager.EnumerationOptions = [], using block: (NSTextElement) -> Bool) -> NSTextLocation? {
guard let from = textLocation as? CustomTextLocation else {
return nil
}
let textElements = textElements(for: NSTextRange(location: from, end: documentRange.endLocation)!)
for textElement in textElements {
let _ = block(textElement)
}
return documentRange.endLocation
}
override func offset(from: NSTextLocation, to: NSTextLocation) -> Int {
guard let from = from as? CustomTextLocation,
let to = to as? CustomTextLocation else {
fatalError("Expected CustomTextLocation")
}
return to.column - from.column
}
}