I’ve got a custom NSLayoutManager
subclass I’m using to draw pill-shaped tokens. I draw these tokens for substrings with a custom attribute (TokenAttribute
). I can draw no problem.
However, I need to add a little bit of “padding” around the ranges with my TokenAttribute
(so that the round rectangle background of the token won’t intersect with the text).
In the above image, I’m drawing my token’s background with an orange colour, but I want extra padding around 469
so the background isn’t right up against the text.
I’m not really sure how to do this. I tried overriding -boundingRectForGlyphRange:inTextContainer:
to return a bounding rect with more horizontal padding, but it appears the layout of glyphs isn’t actually affected by this.
How do I give more spacing around certain glyphs / ranges of glyphs?
Here’s the code I use to draw the background, in my layout manager subclass:
- (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
NSTextStorage *textStorage = self.textStorage;
NSRange glyphRange = glyphsToShow;
while (glyphRange.length > 0) {
NSRange characterRange = [self characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
NSRange attributeCharacterRange;
NSRange attributeGlyphRange;
id attribute = [textStorage attribute:LAYScrubbableParameterAttributeName
atIndex:characterRange.location
longestEffectiveRange:&attributeCharacterRange
inRange:characterRange];
attributeGlyphRange = [self glyphRangeForCharacterRange:attributeCharacterRange
actualCharacterRange:NULL];
attributeGlyphRange = NSIntersectionRange(attributeGlyphRange, glyphRange);
if (attribute != nil) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
UIColor *backgroundColor = [UIColor orangeColor];
NSTextContainer *textContainer = self.textContainers[0];
CGRect boundingRect = [self boundingRectForGlyphRange:attributeGlyphRange inTextContainer:textContainer];
// Offset this bounding rect by the `origin` passed in above
// `origin` is the origin of the text container!
// if we don't do this, then bounding rect is incorrectly placed (too high, in my case).
boundingRect.origin.x += origin.x;
boundingRect.origin.y += origin.y;
[backgroundColor setFill];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:boundingRect cornerRadius:boundingRect.size.height / 2.0];
[path fill];
[super drawGlyphsForGlyphRange:attributeGlyphRange atPoint:origin];
CGContextRestoreGState(context);
} else {
[super drawGlyphsForGlyphRange:glyphsToShow atPoint:origin];
}
glyphRange.length = NSMaxRange(glyphRange) - NSMaxRange(attributeGlyphRange);
glyphRange.location = NSMaxRange(attributeGlyphRange);
}
}
There are methods defined in NSLayoutManagerDelegate, that serve as glyph-based customisation points.
Use
func layoutManager(_ layoutManager: NSLayoutManager, shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>, properties props: UnsafePointer<NSLayoutManager.GlyphProperty>, characterIndexes charIndexes: UnsafePointer<Int>, font aFont: NSFont, forGlyphRange glyphRange: NSRange) -> Int
to identify the glyphs associated with the whitespace surrounding your range-of-interest and mark those by altering their value in the props array to NSLayoutManager.GlyphProperty.controlCharacter. Then pass this altered array to
NSLayoutManager.setGlyphs(_:properties:characterIndexes:font:forGlyphRange:)
Afterwards, you may implement
func layoutManager(_ layoutManager: NSLayoutManager, shouldUse action: NSLayoutManager.ControlCharacterAction, forControlCharacterAt charIndex: Int) -> NSLayoutManager.ControlCharacterAction
to again identify the glyphs of interest and return the predefined action:
NSLayoutManager.ControlCharacterAction.whitespace
This, at the end, lets you implement
func layoutManager(_ layoutManager: NSLayoutManager, boundingBoxForControlGlyphAt glyphIndex: Int, for textContainer: NSTextContainer, proposedLineFragment proposedRect: NSRect, glyphPosition: NSPoint, characterIndex charIndex: Int) -> NSRect
to alter the bounding box used for the glyphs. Simply return the appropriated dimensions. This will have effect on the following layout-mechanism.
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With