My goal is to be able to tap on words accurately…. I’m having a small nightmare with TextKit not providing frames for fragments in the way I’d expect, it’s clear I’m not setting it up correctly but I can’t find the parameter that is causing it.
The NSTextLayoutFragment
it returns is just for the first single line in its entirety, I’d like the frames of each word / character / word fragment / whatever it can do to achieve the goal.
class Label: UILabel {
private var textStorage: NSTextContentStorage? {
guard let attributedText else { return nil }
let textStorage = NSTextContentStorage()
let textContainer = NSTextContainer()
let textLayoutManager = NSTextLayoutManager()
// textContainer.size = bounds.size // neither works, nor does 0.0
textContainer.size = CGSize(width: bounds.size.width,
height: .greatestFiniteMagnitude)
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = lineBreakMode
// textContainer.widthTracksTextView = false // makes no difference
textLayoutManager.textContainer = textContainer
textStorage.addTextLayoutManager(textLayoutManager)
textStorage.textStorage?.append(attributedText)
return textStorage
}
public var characterMap: [CGRect] {
setNeedsLayout()
layoutIfNeeded()
guard let textStorage,
let layoutManager = textStorage.textLayoutManagers.first,
let textContainer = layoutManager.textContainer else { return [] }
layoutManager.enumerateSubstrings(from: textStorage.documentRange.location,
options: [.byWords]) { string, range, textRange, pointer in
print("String:", string, range, textRange)
}
var rects = [CGRect]()
layoutManager.enumerateTextLayoutFragments(from: layoutManager.documentRange.endLocation,
options: [.reverse, .ensuresLayout]) { layoutFragment in
rects.append(layoutFragment.layoutFragmentFrame)
return true
}
print(rects)
return rects
}
Test string:
"Lorem ipsum dolor ⚡️ sit amet, www.google.com consectetur 🌧😭 adipiscing elit"
layoutManager.enumerateSubstrings
works correctly, I get a list of words like this…
String: Optional("Lorem") 0...5 Optional(NSCountableTextRange: {0, 6})
String: Optional("ipsum") 6...11 Optional(NSCountableTextRange: {6, 6})
String: Optional("dolor") 12...17 Optional(NSCountableTextRange: {12, 9})
String: Optional("sit") 21...24 Optional(NSCountableTextRange: {21, 4})
String: Optional("amet") 25...29 Optional(NSCountableTextRange: {25, 6})
String: Optional("www.google.com") 31...45 Optional(NSCountableTextRange: {31, 15})
String: Optional("consectetur") 46...57 Optional(NSCountableTextRange: {46, 17})
String: Optional("adipiscing") 63...73 Optional(NSCountableTextRange: {63, 11})
String: Optional("elit") 74...78 nil
but layoutManager.enumerateTextLayoutFragments
returns only this
[(0.0, 0.0, 357.0078125, 33.4140625)]
What am I missing to get the layoutManager
to fragment the way I’d like it?