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

前端 未结 1 1677
走了就别回头了
走了就别回头了 2021-01-14 20:52

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

\"

相关标签:
1条回答
  • 2021-01-14 21:06

    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];
    
    0 讨论(0)
提交回复
热议问题