Dynamically sizing text with kerning to occupy the full width of a UILabel
Asked Answered
S

1

3

I have a layout with a UILabel placed above a fixed width view, shown below as a grey rectangle.

Text scales well without kerning

The text needs to match the width of the fixed grey view.

I achieved this by setting the adjustsFontSizeToFitWidth property on the UILabel to YES, settings the font size to something really large, and setting the minimumScaleFactor to something suitable small. This worked fine until…

I had to add kerning to said text. I added the kerning by applying @{NSKernAttributeName: @1.40} to an attributed string and then passed the attributed string UILabel’s attributedText property. Unfortunately this seems to stump the automatic scaling, as this results in the text being properly kerned but the end of the string is truncated. It is as though the label scaled the text down without taking kerning into account.

Text truncated when kerning is added

How can I get a given string with kerning to be rendered with a width of my choosing (i.e. the grey view's width)?

Spier answered 18/8, 2014 at 10:18 Comment(2)
have you read this question? #7370513Abner
@Abner I have and said question is not a duplicate. He is actually programatically sizing the label to fit the text. I wish to size the attributed text to fit the label. This may well actually be an oversight of Apple's in which case I will file a Radar.Spier
N
0

I am using next code to calculate kerning (by creating NSString extension).

This extension is using quicksort idea of pivot to quickly find kerning that makes the string fit into the needed width.

Please note that kerning less than -3.0 makes ugly characters overlap, so if string is not fitting with kerning = -3, the algorithm just returns -3. Of course you can set bigKern variable to smaller value.

I checked it against UITabBarItem's (Apple uses kerning on tab bar item labels), and my implementation is very similar.

Hope you like it.

@implementation NSString (Extension)

- (CGFloat)kernForFont:(UIFont *)font toFitWidth:(CGFloat)width
{
    CGSize size = CGSizeMake(CGFLOAT_MAX, font.pointSize*2); // Size to fit.

    const CGFloat threshold = 0.1;
    CGFloat bigKern = -3.0, smallKern = 0.0, pivot = 0.0;
    NSMutableDictionary *attrs = [NSMutableDictionary new];
    attrs[NSFontAttributeName] = font;

    while (true) {
        attrs[NSKernAttributeName] = @(pivot);

        CGRect frame = [self boundingRectWithSize:size
                                          options:NSStringDrawingUsesLineFragmentOrigin
                                       attributes:attrs
                                          context:nil];
        CGFloat diff = width - frame.size.width;
        if (diff > -0.5) {
            // String is fitting.
            if (pivot == 0.0) // Fits without kerning.
                return pivot;
            else if (smallKern - bigKern <= threshold)
                return pivot; // Threshold is reached, return the fitting pivot.
            else {
                // Pivot is fitting, but threshold is not reached, set pivot as max.
                bigKern = pivot;
            }
        }
        else {
            // String not fitting.
            smallKern = pivot;
            if (smallKern - bigKern <= threshold)
                return bigKern;
        }
        pivot = (smallKern + bigKern) / 2.0;
    }

    return bigKern;
}

@end

Example usage, for custom UITabBarItems:

// I have a tabBarItem of type UITabBarItem. textColor is a UIColor.
NSString *title = tabBarItem.title;

CGFloat textLabelWidth = tabBar.frame.size.width / (CGFloat)(self.tabBar.items.count) - 6.0; // 6 is padding.

UIFont *font = [UIFont systemFontOfSize:10.0];
CGFloat kern = [title kernForFont:font toFitWidth:textLabelWidth];

NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter;

NSDictionary *attrs = @{
                        NSFontAttributeName: font,
                        NSKernAttributeName: @(kern),
                        NSForegroundColorAttributeName: textColor,
                        NSParagraphStyleAttributeName: paragraphStyle
                        };
textLabel.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attrs];
Nicolenicolea answered 1/10, 2017 at 22:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.