Hey folks – I'm new to TextKit and trying to draw backgrounds & borders around specific attributes. I've gotten fairly close, but haven't yet found methods that don't generate very inconsistent sizing, which tends to look bad. Here's my first crack at it:
class MyLayout: NSLayoutManager {
override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
super.drawBackground(forGlyphRange: glyphsToShow, at: origin)
guard let storage = textStorage else {
return
}
guard let context = UIGraphicsGetCurrentContext() else {
return
}
var codeBlockRect: CGRect? = nil
enumerateLineFragments(forGlyphRange: glyphsToShow) { (rect, usedRect, container, subRange, stop) in
var effectiveRange = NSRange()
let attributes = storage.attributes(at: subRange.location, effectiveRange: &effectiveRange)
storage.enumerateAttribute(.inlineCodeBlock, in: subRange) { (value, attributeRange, stop) in
guard value != nil else {
return
}
var background = self.boundingRect(forGlyphRange: attributeRange, in: container)
background.origin.x += origin.x
background.origin.y += origin.y
context.setFillColor(UIColor.lightGrey.cgColor)
context.setStrokeColor(UIColor.mediumGrey.cgColor)
context.stroke(background)
context.fill(background)
}
}
}
}
That produces these results:
As you can see, there's about 3 pixels of difference between the sizes there. I imagine it's because boundingRect
, as the documentation says:
Returns the smallest bounding rect which completely encloses the glyphs in the given glyphRange
But I haven't found a method that gives me a number closer to what I'm looking for. My ideal scenario is that every rectangle will have the exact same height.
Let me know if any more information is needed.
It crossed my mind that this could be related to the proprietary font we're using, so I changed everything to use UIFont.systemFont, which didn't make any difference.
I found a workaround here – instead of defining my inlineCodeBlock
as a background, i defined it as a custom underline style.
Doing that let me override drawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:)
.
Once I had that, I was able to use baselineOffset
to get a consistent positioning.