Dynamically changing font size of UILabel

I currently have a UILabel:

factLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 100, 280, 100)];
factLabel.text = @"some text some text some text some text";
factLabel.backgroundColor = [UIColor clearColor];
factLabel.lineBreakMode = UILineBreakModeWordWrap;
factLabel.numberOfLines = 10;
[self.view addSubview:factLabel];

Throughout the life of my iOS application, factLabel gets a bunch of different values. Some with multiple sentences, others with just 5 or 6 words.

  • How do I calculate the UILabel height dynamically
  • UIButton that resizes to fit its titleLabel
  • Add a regular action to an NSAttributedString?
  • UIButton with two lines of text in the title (numberOfLines=2)
  • fatal error when prepared to segue to view controller
  • sizeToFit is functioning oddly
  • How can I set up the UILabel so that the font size changes so that the text always fits in the bounds I defined?

    13 Solutions Collect From Internet About “Dynamically changing font size of UILabel”

    Single line:

    factLabel.numberOfLines = 1;
    factLabel.minimumFontSize = 8;
    factLabel.adjustsFontSizeToFitWidth = YES;
    

    The above code will adjust your text’s font size down to (for example) 8 trying to fit your text within the label.
    numberOfLines = 1 is mandatory.

    Multiple lines:

    For numberOfLines > 1 there is a method to figure out the size of final text through NSString’s UIKit addition methods, for example:

    CGSize lLabelSize = [yourText sizeWithFont: factLabel.font forWidth:factLabel.frame.size.width lineBreakMode:factLabel.lineBreakMode];
    

    After that you can just resize your label using resulting lLabelSize, for example (assuming that you will change only label’s height):

    factLabel.frame = CGRectMake(factLabel.frame.origin.x, factLabel.frame.origin.y, factLabel.frame.size.width, lLabelSize.height);
    

    iOS6

    Single line:

    Starting with iOS6, minimumFontSize has been deprecated. The line

    factLabel.minimumFontSize = 8.;
    

    can be changed to:

    factLabel.minimumScaleFactor = 8./factLabel.font.pointSize;
    

    iOS7

    Multiple lines:

    Starting with iOS7, sizeWithFont becomes deprecated.
    Multiline case is reduced to:

    factLabel.numberOfLines = 0;
    factLabel.lineBreakMode = NSLineBreakByWordWrapping;
    CGSize maximumLabelSize = CGSizeMake(factLabel.frame.size.width, CGFLOAT_MAX);
    CGSize expectSize = [factLabel sizeThatFits:maximumLabelSize];
    factLabel.frame = CGRectMake(factLabel.frame.origin.x, factLabel.frame.origin.y, expectSize.width, expectSize.height);
    

    minimumFontSize has been deprecated with iOS 6. You can use minimumScaleFactor.

    yourLabel.adjustsFontSizeToFitWidth=YES;
    yourLabel.minimumScaleFactor=0.5;
    

    This will take care of your font size according width of label and text.

    Based on @Eyal Ben Dov’s answer you may want to create a category to make it flexible to use within another apps of yours.

    Obs.: I’ve updated his code to make compatible with iOS 7

    -Header file

    #import <UIKit/UIKit.h>
    
    @interface UILabel (DynamicFontSize)
    
    -(void) adjustFontSizeToFillItsContents;
    
    @end
    

    -Implementation file

    #import "UILabel+DynamicFontSize.h"
    
    @implementation UILabel (DynamicFontSize)
    
    #define CATEGORY_DYNAMIC_FONT_SIZE_MAXIMUM_VALUE 35
    #define CATEGORY_DYNAMIC_FONT_SIZE_MINIMUM_VALUE 3
    
    -(void) adjustFontSizeToFillItsContents
    {
        NSString* text = self.text;
    
        for (int i = CATEGORY_DYNAMIC_FONT_SIZE_MAXIMUM_VALUE; i>CATEGORY_DYNAMIC_FONT_SIZE_MINIMUM_VALUE; i--) {
    
            UIFont *font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
            NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: font}];
    
            CGRect rectSize = [attributedText boundingRectWithSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
    
            if (rectSize.size.height <= self.frame.size.height) {
                self.font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
                break;
            }
        }
    
    }
    
    @end
    

    -Usage

    #import "UILabel+DynamicFontSize.h"
    
    [myUILabel adjustFontSizeToFillItsContents];
    

    Cheers

    It’s 2015. I had to go to find a blog post that would explain how to do it for the latest version of iOS and XCode with Swift so that it would work with multiple lines.

    1. set “Autoshrink” to “Minimum font size.”
    2. set the font to the largest desirable font size (I chose 20)
    3. Change “Line Breaks” from “Word Wrap” to “Truncate Tail.”

    Source:
    http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/

    Swift version:

    textLabel.adjustsFontSizeToFitWidth = true
    textLabel.minimumScaleFactor = 0.5
    

    Here’s a Swift extension for UILabel. It runs a binary search algorithm to resize the font based off the width and height of the label’s bounds. Tested to work with iOS 9 and autolayout.

    USAGE: Where <label> is your pre-defined UILabel that needs font resizing

    <label>.fitFontForSize()
    

    By Default, this function searches in within the range of 5pt and 300pt font sizes and sets the font to fit its text “perfectly” within the bounds (accurate within 1.0pt). You could define the parameters so that it, for example, searches between 1pt and the label’s current font size accurately within 0.1pts in the following way:

    <label>.fitFontForSize(1.0, maxFontSize: <label>.font.pointSize, accuracy:0.1)
    

    Copy/Paste the following code into your file

    extension UILabel {
    
        func fitFontForSize(var minFontSize : CGFloat = 5.0, var maxFontSize : CGFloat = 300.0, accuracy : CGFloat = 1.0) {
            assert(maxFontSize > minFontSize)
            layoutIfNeeded() // Can be removed at your own discretion
            let constrainedSize = bounds.size
            while maxFontSize - minFontSize > accuracy {
                let midFontSize : CGFloat = ((minFontSize + maxFontSize) / 2)
                font = font.fontWithSize(midFontSize)
                sizeToFit()
                let checkSize : CGSize = bounds.size
                if  checkSize.height < constrainedSize.height && checkSize.width < constrainedSize.width {
                    minFontSize = midFontSize
                } else {
                    maxFontSize = midFontSize
                }
            }
            font = font.fontWithSize(minFontSize)
            sizeToFit()
            layoutIfNeeded() // Can be removed at your own discretion
        }
    
    }
    

    NOTE: Each of the layoutIfNeeded() calls can be removed at your own discretion

    Its a little bit not sophisticated but this should work,
    for example lets say you want to cap your uilabel to 120×120, with max font size of 28:

    magicLabel.numberOfLines = 0;
    magicLabel.lineBreakMode = NSLineBreakByWordWrapping;
    ...
    magicLabel.text = text;
        for (int i = 28; i>3; i--) {
            CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:(CGFloat)i] constrainedToSize:CGSizeMake(120.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
            if (size.height < 120) {
                magicLabel.font = [UIFont systemFontOfSize:(CGFloat)i];
                break;
            }
        }
    

    Just send the sizeToFit message to the UITextView. It will adjust its own height to just fit its text. It will not change its own width or origin.

    [textViewA1 sizeToFit];
    

    This solution works for multiline:

    After following several articles, and requiring a function that would automatically scale the text and adjust the line count to best fit within the given label size, I wrote a function myself. (ie. a short string would fit nicely on one line and use a large amount of the label frame, whereas a long strong would automatically split onto 2 or 3 lines and adjust the size accordingly)

    Feel free to re-use it and tweak as required. Make sure you call it after viewDidLayoutSubviews has finished so that the initial label frame has been set.

    + (void)setFontForLabel:(UILabel *)label withMaximumFontSize:(float)maxFontSize andMaximumLines:(int)maxLines {
        int numLines = 1;
        float fontSize = maxFontSize;
        CGSize textSize; // The size of the text
        CGSize frameSize; // The size of the frame of the label
        CGSize unrestrictedFrameSize; // The size the text would be if it were not restricted by the label height
        CGRect originalLabelFrame = label.frame;
    
        frameSize = label.frame.size;
        textSize = [label.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize: fontSize]}];
    
        // Work out the number of lines that will need to fit the text in snug
        while (((textSize.width / numLines) / (textSize.height * numLines) > frameSize.width / frameSize.height) && (numLines < maxLines)) {
            numLines++;
        }
    
        label.numberOfLines = numLines;
    
        // Get the current text size
        label.font = [UIFont systemFontOfSize:fontSize];
        textSize = [label.text boundingRectWithSize:CGSizeMake(frameSize.width, CGFLOAT_MAX)
                                            options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                         attributes:@{NSFontAttributeName : label.font}
                                            context:nil].size;
    
        // Adjust the frame size so that it can fit text on more lines
        // so that we do not end up with truncated text
        label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, label.frame.size.width, label.frame.size.width);
    
        // Get the size of the text as it would fit into the extended label size
        unrestrictedFrameSize = [label textRectForBounds:CGRectMake(0, 0, label.bounds.size.width, CGFLOAT_MAX) limitedToNumberOfLines:numLines].size;
    
        // Keep reducing the font size until it fits
        while (textSize.width > unrestrictedFrameSize.width || textSize.height > frameSize.height) {
            fontSize--;
            label.font = [UIFont systemFontOfSize:fontSize];
            textSize = [label.text boundingRectWithSize:CGSizeMake(frameSize.width, CGFLOAT_MAX)
                                                options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                             attributes:@{NSFontAttributeName : label.font}
                                                context:nil].size;
            unrestrictedFrameSize = [label textRectForBounds:CGRectMake(0, 0, label.bounds.size.width, CGFLOAT_MAX) limitedToNumberOfLines:numLines].size;
        }
    
        // Set the label frame size back to original
        label.frame = originalLabelFrame;
    }
    

    Single line– There are two ways, you can simply change.

    1- Pragmatically (Swift 3)

    Just add the following code

        yourLabel.numberOfLines = 1;
        yourLabel.minimumScaleFactor = 0.7;
        yourLabel.adjustsFontSizeToFitWidth = true;
    

    2 – Using UILabel Attributes inspector

    i- Select your label- Set number of lines 1.
    ii- Autoshrink-  Select Minimum Font Scale from drop down
    iii- Set Minimum Font Scale value as you wish , I have set 0.7 as in below image. (default is 0.5)
    

    enter image description here

    Swift 2.0 Version:

    private func adapteSizeLabel(label: UILabel, sizeMax: CGFloat) {
         label.numberOfLines = 0
         label.lineBreakMode = NSLineBreakMode.ByWordWrapping
         let maximumLabelSize = CGSizeMake(label.frame.size.width, sizeMax);
         let expectSize = label.sizeThatFits(maximumLabelSize)
         label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, expectSize.width, expectSize.height)
    }
    

    Here is the fill code of a UILabel subclass that implements animated font size change:

    @interface SNTextLayer : CATextLayer
    
    @end
    
    @implementation SNTextLayer
    
    - (void)drawInContext:(CGContextRef)ctx {
        // We override this to make text appear at the same vertical positon as in UILabel
        // (otherwise it's shifted tdown)
        CGFloat height = self.bounds.size.height;
        float fontSize = self.fontSize;
        // May need to adjust this somewhat if it's not aligned perfectly in your implementation
        float yDiff = (height-fontSize)/2 - fontSize/10;
    
        CGContextSaveGState(ctx);
        CGContextTranslateCTM(ctx, 0.0, yDiff);
        [super drawInContext:ctx];
         CGContextRestoreGState(ctx);
    }
    
    @end
    
    @interface SNAnimatableLabel ()
    
    @property CATextLayer* textLayer;
    
    @end
    
    @interface SNAnimatableLabel : UILabel
    
    - (void)animateFontToSize:(CGFloat)fontSize withDuration:(double)duration;
    
    @end
    
    
    
    @implementation SNAnimatableLabel
    
    
    - (void)awakeFromNib {
        [super awakeFromNib];
        _textLayer = [SNTextLayer new];
        _textLayer.backgroundColor = self.backgroundColor.CGColor;
        _textLayer.foregroundColor = self.textColor.CGColor;
        _textLayer.font = CGFontCreateWithFontName((CFStringRef)self.font.fontName);
        _textLayer.frame = self.bounds;
        _textLayer.string = self.text;
        _textLayer.fontSize = self.font.pointSize;
        _textLayer.contentsScale = [UIScreen mainScreen].scale;
        [_textLayer setPosition: CGPointMake(CGRectGetMidX(_textLayer.frame), CGRectGetMidY(_textLayer.frame))];
        [_textLayer setAnchorPoint: CGPointMake(0.5, 0.5)];
        [_textLayer setAlignmentMode: kCAAlignmentCenter];
        self.textColor = self.backgroundColor;
        // Blend text with background, so that it doens't interfere with textlayer text
        [self.layer addSublayer:_textLayer];
        self.layer.masksToBounds = NO;
    }
    
    - (void)setText:(NSString *)text {
        _textLayer.string = text;
        super.text = text;
    }
    
    - (void)layoutSubviews {
        [super layoutSubviews];
        // Need to enlarge the frame, otherwise the text may get clipped for bigger font sizes
        _textLayer.frame = CGRectInset(self.bounds, -5, -5);
    }
    
    - (void)animateFontToSize:(CGFloat)fontSize withDuration:(double)duration {
        [CATransaction begin];
        [CATransaction setAnimationDuration:duration];
        _textLayer.fontSize = fontSize;
        [CATransaction commit];
    }
    

    Simple extension, just adjust the max and min.

    extension UILabel {
        func adjustFontSizeToHeight()
        {
            // Initial size is max and the condition the min.
            for var size = 25 ; size >= 4 ; size--
            {
                let font = UIFont(name: self.font!.fontName, size: CGFloat(size))!
    
                let attrString = NSAttributedString(string: self.text!, attributes: [NSFontAttributeName : font])
    
                let rectSize = attrString.boundingRectWithSize(CGSizeMake(self.bounds.width, CGFloat.max), options: .UsesLineFragmentOrigin, context: nil)
    
                if rectSize.size.height <= self.bounds.height
                {
                    self.font = font
                    break
                }
            }
        }
    }