213

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 can I set up the UILabel so that the font size changes so that the text always fits in the bounds I defined?

CodeGuy
  • 28,427
  • 76
  • 200
  • 317
  • 2
    For 2016, I really believe the only *good* solution is to use the "use autoshrinking" approach. Make the UILabel box the actual size you want, make the font fill the UILabel, select autoshrink, set a titular huge font size (300), and be sure to test on the smallest/largest simulators. (So, 4s/PadPro currently.) Full explanation: http://stackoverflow.com/a/35154493/294884 This is the only real solution today. – Fattie May 16 '16 at 15:43

12 Answers12

407

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 sizeWithFont:... 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);

iOS 13 (Swift 5):

label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.5
luke
  • 2,743
  • 4
  • 19
  • 43
Martin Babacaev
  • 6,240
  • 2
  • 19
  • 34
  • but this puts the text all on one line. and if I change the factLabel.numberOfLines, then the font size does not change dynamically. – CodeGuy Feb 01 '11 at 17:06
  • @reising1: you're right. This is just how to make framework to do resizing work for you. – Martin Babacaev Feb 01 '11 at 17:10
  • so then the answer to my question is that there is no way to do it using the provided framework? – CodeGuy Feb 01 '11 at 17:12
  • But I don't want to change the size of my Label. I want to keep the label consistent sized and change the size of the font. – CodeGuy Feb 01 '11 at 17:27
  • 1
    @reising1: In this case you also can use NSString UIKit addition's method: `sizeWithFont:constrainedToSize:lineBreakMode:` But this way is a little bit difficult – Martin Babacaev Feb 01 '11 at 17:31
  • The only way I can see, is to iterate through different font sizes and compare resulting computed size with label size (or height only). Using `sizeWithFont:constrainedToSize:lineBreakMode:` of course – Martin Babacaev Feb 01 '11 at 17:44
  • Would you mind showing me the code for this? Let's say I have NSString *str and my UILabel has the frame in the code above. Can you show me code that spits out a font size? – CodeGuy Feb 01 '11 at 17:49
  • 7
    It's deprecated since iOS6. Replace it with `myLabel.minimumScaleFactor:10.0/[UIFont labelFontSize];` – Norbert Sep 21 '13 at 17:11
  • XCode tells me labelFontSize doesn't exist, I tried 'factLabel.minimumScaleFactor = 8./self.factLabel.font.pointSize;' – Dulgan Apr 02 '14 at 15:14
  • @MartinBabacaev numberOfLines = 1 is NOT mandatory, at least in iOS7+. – JRam13 Sep 30 '14 at 20:31
  • are you joking? `adjustsFontSizeToFitWidth` only reduces text if it doesn't fit within container – user25 Mar 23 '19 at 22:18
76

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.

Cesare
  • 9,139
  • 16
  • 78
  • 130
Amit Singh
  • 2,644
  • 21
  • 20
  • I usually use 0.8, because even 0.7 tends to look too small. Of course some text may not fit with minimum scale factor 0.8, it's a matter of deciding what looks better and where things get unreadable. OTOH my apps can be rotated which helps a lot. – gnasher729 May 23 '15 at 13:18
  • 1
    `adjustsFontSizeToFitWidth` only reduces text if it doesn't fit within container – user25 Mar 23 '19 at 22:18
28

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

Devendra Singh
  • 717
  • 1
  • 13
  • 14
25

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

Paulo Miguel Almeida
  • 2,114
  • 31
  • 36
  • it's not working for me. The content of my UILabel is cut off now. – Adrian Apr 10 '14 at 16:49
  • 1
    If it's not working for you, it's probably because the frame of the label isn't set yet. Try setting the frame before calling this (or call `setNeedsLayout`/`layoutIfNeeded` if you're using AutoLayout). – bmueller Jul 07 '14 at 18:31
  • It gives the following crash "' NSInvalidArgumentException', reason: 'NSConcreteAttributedString initWithString:: nil value'" – Mohamed Saleh May 22 '15 at 14:46
  • It means that your NSString can't be nil. I'm assuming that if you want to adjust font size to fill UILabel's content you have at least to provide a text. – Paulo Miguel Almeida May 23 '15 at 20:40
  • This has a drawback. It line breaks between characters, so you see the words split into different lines. Is there a way to circumvent this? – Özgür Feb 17 '16 at 20:02
  • Hi @Özgür, have you tried to set your label do line break by word wrapping like this ? ```label.lineBreakMode = .ByWordWrapping``` – Paulo Miguel Almeida Feb 18 '16 at 00:34
  • @PauloMiguelAlmeida yes, I did. I even tried that by adding a NSAttributedString just for this, i.e NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; [style setLineBreakMode:NSLineBreakByWordWrapping]; – Özgür Feb 18 '16 at 06:57
  • @Özgür I see. I'll look into this when I get home and I let you know if I managed to solve this. – Paulo Miguel Almeida Feb 18 '16 at 12:34
  • To get accurate result call this method from `layoutSubviews` of label's superview – schmidt9 Dec 13 '16 at 21:25
25

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/

Gabriel Garrett
  • 2,087
  • 6
  • 27
  • 45
  • 2
    Super cool.. That truncate tail point is the most important.. Coz in case of word wrap autolayout doesn't feel the urge to decrease the font size, whereas when it is truncate tail autolayout has to save the text from the blade and it is then that it resizes the font. – GKK Aug 10 '16 at 06:01
  • Very true, without truncate tail minimun font size doesn't work – Gustavo Baiocchi Costa Sep 28 '22 at 11:34
15

Swift version:

textLabel.adjustsFontSizeToFitWidth = true
textLabel.minimumScaleFactor = 0.5
Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
7

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

Avi Frankl
  • 211
  • 4
  • 6
4

Its a little bit not sophisticated but this should work, for example lets say you want to cap your uilabel to 120x120, 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;
        }
    }
  • This seems rather inefficient - you should let the UILabel dynamically height itself to fit in some provided available space. If you run this for something like a table view cell's title font calculation, you will get major lagging issues. The approach may work, but definitely not recommended. – Zorayr Feb 13 '15 at 22:52
  • Up-vote for being the only person to actually answer the question. – Jai Mar 23 '15 at 02:47
2

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];
codercat
  • 22,873
  • 9
  • 61
  • 85
  • What happens when the size that fits the text is too large for the container's space? For example, let's say you have 100 points available to fit the text view, after calling `sizeToFit` your `textViewA1` becomes 200 points which ends up getting cropped. – Zorayr Feb 13 '15 at 22:50
0

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)
}
Phil
  • 4,730
  • 1
  • 41
  • 39
0

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;
}
aaroncatlin
  • 3,203
  • 1
  • 16
  • 27
0

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];
}
videolist
  • 219
  • 3
  • 4