Search code examples
iosswifttextkit

getting consistent sizing for glyph backgrounds


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:

Single line of text:
Single line of text

Multiple lines of text:
Multiple lines of text

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.

Update

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.


Solution

  • 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.