Dynamically changing font size of UILabel
Asked Answered
N

12

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?

Neaten answered 1/2, 2011 at 16:53 Comment(1)
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: https://mcmap.net/q/128589/-how-to-dynamically-change-the-font-size-in-auto-layout-ios This is the only real solution today.Malnutrition
S
408

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
Starcrossed answered 1/2, 2011 at 17:3 Comment(11)
but this puts the text all on one line. and if I change the factLabel.numberOfLines, then the font size does not change dynamically.Neaten
@reising1: you're right. This is just how to make framework to do resizing work for you.Starcrossed
so then the answer to my question is that there is no way to do it using the provided framework?Neaten
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.Neaten
@reising1: In this case you also can use NSString UIKit addition's method: sizeWithFont:constrainedToSize:lineBreakMode: But this way is a little bit difficultStarcrossed
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 courseStarcrossed
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?Neaten
It's deprecated since iOS6. Replace it with myLabel.minimumScaleFactor:10.0/[UIFont labelFontSize];Bethune
XCode tells me labelFontSize doesn't exist, I tried 'factLabel.minimumScaleFactor = 8./self.factLabel.font.pointSize;'Dari
@MartinBabacaev numberOfLines = 1 is NOT mandatory, at least in iOS7+.Gribble
are you joking? adjustsFontSizeToFitWidth only reduces text if it doesn't fit within containerIambus
V
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.

Vandenberg answered 19/2, 2013 at 10:47 Comment(2)
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.Matriculate
adjustsFontSizeToFitWidth only reduces text if it doesn't fit within containerIambus
B
30

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

Bran answered 4/8, 2017 at 18:34 Comment(1)
Much less trouble to do it in Interface Builder, so it's wonderful that you mentioned both approaches!Skidway
E
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

Evered answered 27/1, 2014 at 13:30 Comment(9)
it's not working for me. The content of my UILabel is cut off now.Routh
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).Abutilon
It gives the following crash "' NSInvalidArgumentException', reason: 'NSConcreteAttributedString initWithString:: nil value'"Guadalcanal
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.Evered
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?Missis
Hi @Özgür, have you tried to set your label do line break by word wrapping like this ? label.lineBreakMode = .ByWordWrappingEvered
@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];Missis
@Özgür I see. I'll look into this when I get home and I let you know if I managed to solve this.Evered
To get accurate result call this method from layoutSubviews of label's superviewSparge
R
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/

Ronrona answered 14/6, 2015 at 4:16 Comment(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.Archaize
Very true, without truncate tail minimun font size doesn't workCooper
N
15

Swift version:

textLabel.adjustsFontSizeToFitWidth = true
textLabel.minimumScaleFactor = 0.5
Neff answered 2/6, 2015 at 21:21 Comment(1)
Thanks.. Seems like here sequence also matersBenzol
T
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

Transsonic answered 31/12, 2015 at 2:47 Comment(1)
Ah - but it doesn't really work with autolayout; the "sizeToFit"s do nothing in that case.Malnutrition
V
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;
        }
    }
Villagomez answered 11/6, 2013 at 22:41 Comment(2)
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.Teenateenage
Up-vote for being the only person to actually answer the question.Hypso
F
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];
Falkner answered 20/11, 2012 at 4:13 Comment(1)
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.Teenateenage
G
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)
}
Goidelic answered 16/3, 2016 at 9:27 Comment(0)
B
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;
}
Bushwhack answered 10/4, 2016 at 12:57 Comment(0)
B
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];
}
Brost answered 30/5, 2016 at 22:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.