I am trying to apply some custom behaviour to how individual lines are drawn using textkit2, so I have an custom class that subclasses NSTextLayoutFragment:
class MyCustomLayoutFragment: NSTextLayoutFragment {
override var textLineFragments: [NSTextLineFragment] {
super.textLineFragments.map {
NSTextLineFragment(
attributedString: $0.attributedString,
range: $0.characterRange)
}
}
}
The above is the entire code for my layout fragment, which I expected to just work as I am creating exact copies of the original text line fragments.
However, when I have the above no text is drawn at all! If I simply return super.textLineFragments everything works normally again.
What am I missing here?
NSTextLineFragment is not something you’re supposed to recreate manually. These objects are produced as a result of the layout process inside NSTextLayoutManager and contain internal state that is not exposed through public initializers. So when you copy a line fragment using init(attributedString:range:), what you actually get is an empty shell that has no correct glyph positions, no baseline, etc.
If you need to influence how lines are drawn, don’t touch textLineFragments at all and work with the ones that have already been created. Try overriding draw(at:in:) in your NSTextLayoutFragment subclass and iterate over super.textLineFragments there. At that point you can draw any decorative elements you want, and then call line.draw(...).
override func draw(at point: CGPoint, in context: CGContext) {
for line in super.textLineFragments {
let rect = line.typographicBounds
.offsetBy(dx: point.x, dy: point.y)
context.setFillColor(UIColor.yellow.withAlphaComponent(0.2).cgColor)
context.fill(rect)
line.draw(
at: CGPoint(
x: line.typographicBounds.origin.x + point.x,
y: line.typographicBounds.origin.y + point.y
),
in: context
)
}
}
point is the origin of the entire layout fragment, and typographicBounds of a line are its local coordinates within that fragment. If you don’t actually need to work with line geometry and just want to change the appearance of the text, you don’t need to subclass at all, you can work just with NSAttributedString.
let attr = NSMutableAttributedString(string: "Hello TextKit 2")
attr.addAttributes(
[
.backgroundColor: UIColor.yellow,
.underlineStyle: NSUnderlineStyle.single.rawValue
],
range: NSRange(location: 0, length: 5)
)