How to adjust font size of label to fit the rectangle?

前端 未结 14 773
独厮守ぢ
独厮守ぢ 2020-11-28 07:00

Yeah, there\'s this cool myLabel.adjustsFontSizeToFitWidth = YES; property. But as soon as the label has two lines or more, it won\'t resize the text to anythin

14条回答
  •  误落风尘
    2020-11-28 07:01

    This solution (based on this answer) works with auto-layout and performs a binary search to find the best font size.

    The only caveat I have found is that you can't specify the number of lines (because AFAIK you can't tell boundingRectWithSize how many lines you want).

    AdjustableLabel.h

    #import 
    
    @interface AdjustableLabel : UILabel
    /** 
      If set to YES, font size will be automatically adjusted to frame.
      Note: numberOfLines can't be specified so it will be set to 0.
    */
    @property(nonatomic) BOOL adjustsFontSizeToFitFrame;
    @end
    

    AdjustableLabel.m

    #import "AdjustableLabel.h"
    
    @interface AdjustableLabel ()
    @property(nonatomic) BOOL fontSizeAdjusted;
    @end
    
    // The size found S satisfies: S fits in the frame and and S+DELTA doesn't.
    #define DELTA 0.5
    
    @implementation AdjustableLabel
    
    - (void)setAdjustsFontSizeToFitFrame:(BOOL)adjustsFontSizeToFitFrame
    {
        _adjustsFontSizeToFitFrame = adjustsFontSizeToFitFrame;
    
        if (adjustsFontSizeToFitFrame) {
            self.numberOfLines = 0; // because boundingRectWithSize works like this was 0 anyway
        }
    }
    
    - (void)layoutSubviews
    {
        [super layoutSubviews];
    
        if (self.adjustsFontSizeToFitFrame && !self.fontSizeAdjusted)
        {
            self.fontSizeAdjusted = YES; // to avoid recursion, because adjustFontSizeToFrame will trigger this method again
    
            [self adjustFontSizeToFrame];
        }
    }
    
    - (void) adjustFontSizeToFrame
    {
        UILabel* label = self;
    
        if (label.text.length == 0) return;
    
        // Necessary or single-char texts won't be correctly adjusted
        BOOL checkWidth = label.text.length == 1;
    
        CGSize labelSize = label.frame.size;
    
        // Fit label width-wise
        CGSize constraintSize = CGSizeMake(checkWidth ? MAXFLOAT : labelSize.width, MAXFLOAT);
    
        // Try all font sizes from largest to smallest font size
        CGFloat maxFontSize = 300;
        CGFloat minFontSize = 5;
    
        NSString* text = label.text;
        UIFont* font = label.font;
    
        while (true)
        {
            // Binary search between min and max
            CGFloat fontSize = (maxFontSize + minFontSize) / 2;
    
            // Exit if approached minFontSize enough
            if (fontSize - minFontSize < DELTA/2) {
                font = [UIFont fontWithName:font.fontName size:minFontSize];
                break; // Exit because we reached the biggest font size that fits
            } else {
                font = [UIFont fontWithName:font.fontName size:fontSize];
            }
    
            // Find label size for current font size
            CGRect rect = [text boundingRectWithSize:constraintSize
                                             options:NSStringDrawingUsesLineFragmentOrigin
                                          attributes:@{NSFontAttributeName : font}
                                             context:nil];
    
            // Now we discard a half
            if( rect.size.height <= labelSize.height && (!checkWidth || rect.size.width <= labelSize.width) ) {
                minFontSize = fontSize; // the best size is in the bigger half
            } else {
                maxFontSize = fontSize; // the best size is in the smaller half
            }
        }
    
        label.font = font;
    }
    
    @end
    

    Usage

    AdjustableLabel* label = [[AdjustableLabel alloc] init];
    label.adjustsFontSizeToFitFrame = YES;
    
    // In case you change the font, the size you set doesn't matter
    label.font = [UIFont fontWithName:@"OpenSans-Light" size:20];
    

提交回复
热议问题