How to adjust font size of label to fit the rectangle?
Asked Answered
M

14

37

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 anything. So it just gets truncated with ... if it doesn't fit into the rect.

Is there another way to do it?

Marybellemarybeth answered 16/5, 2010 at 15:58 Comment(0)
F
51

If you want to make sure the label fits in the rectangle both width and height wise you can try different font size on the label to see if one will fit.

This snippet starts at 300 pt and tries to fit the label in the targeted rectangle by reducing the font size.

- (void) sizeLabel: (UILabel *) label toRect: (CGRect) labelRect {

    // Set the frame of the label to the targeted rectangle
    label.frame = labelRect;

    // Try all font sizes from largest to smallest font size
    int fontSize = 300;
    int minFontSize = 5;

    // Fit label width wize
    CGSize constraintSize = CGSizeMake(label.frame.size.width, MAXFLOAT);

    do {
        // Set current font size
        label.font = [UIFont fontWithName:label.font.fontName size:fontSize];

        // Find label size for current font size
        CGRect textRect = [[label text] boundingRectWithSize:constraintSize
                                                     options:NSStringDrawingUsesLineFragmentOrigin
                                                  attributes:@{NSFontAttributeName: label.font}
                                                     context:nil];

        CGSize labelSize = textRect.size;

        // Done, if created label is within target size
        if( labelSize.height <= label.frame.size.height )
            break;

        // Decrease the font size and try again
        fontSize -= 2;

    } while (fontSize > minFontSize);
}

I think the above explains what goes on. A faster implementation could use caching and argarcians binary search as follows

+ (CGFloat) fontSizeForString: (NSString*) s inRect: (CGRect) labelRect  {
    // Cache repeat queries
    static NSMutableDictionary* mutableDict = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutableDict = [NSMutableDictionary dictionary];
    });

    NSString* key = [NSString stringWithFormat:@"%@_%d_%d", s, (int) labelRect.size.width, (int) labelRect.size.height];
    NSNumber* value = [mutableDict objectForKey:key];
    if (value)
        return value.doubleValue;

    // Set the frame of the label to the targeted rectangle
    UILabel* label = [[UILabel alloc] init];
    label.text = s;
    label.frame = labelRect;

    // Hopefully between 5 and 300
    CGFloat theSize = (CGFloat) [self binarySearchForFontSizeForLabel:label withMinFontSize:5 withMaxFontSize:300 withSize:label.frame.size];
    [mutableDict setObject:@(theSize) forKey:key];
    return  theSize;
}


+ (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
    // If the sizes are incorrect, return 0, or error, or an assertion.
    if (maxFontSize < minFontSize) {
        return maxFontSize;
    }

    // Find the middle
    NSInteger fontSize = (minFontSize + maxFontSize) / 2;
    // Create the font
    UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
    // Create a constraint size with max height
    CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
    // Find label size for current font size
    CGRect rect = [label.text boundingRectWithSize:constraintSize
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                        attributes:@{NSFontAttributeName : font}
                                           context:nil];
    CGSize labelSize = rect.size;

    // EDIT:  The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
    if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
        return fontSize;
    } else if (labelSize.height > size.height || labelSize.width > size.width) {
        return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
    } else {
        return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
    }
}
Flunkey answered 9/6, 2010 at 10:55 Comment(5)
After searching for hours and hours, this has been the only solution that worked for me. I just wonder what is the performance hit of the while loop when you have 50 - 100 labels on a view.Well
I never had any reason to optimise it. I don't use it to recalculate over and over. The only place i have a lot of labels to size for I pick the longest label text and size for that.Flunkey
@NielsCastle does your solution apply word wrapping technique as well? I wrote a case in agarcian's answer. Does it fulfil that case as well?Premium
I use it for short labels. YMMW. Try setting the lineBreakMode on the incoming label.Flunkey
This is horribly inefficient, especially when resizing.Karyosome
W
32

I found Niels' answer to be the best answer for this issue. However, I have a UIView that can have 100 labels where I need to fit the text, so this process was very inefficient and I could feel the hit in performance.

Here is his code modified to use a binary search instead, rather than a linear search. Now it works very efficiently.

- (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size {
    // If the sizes are incorrect, return 0, or error, or an assertion.
    if (maxFontSize < minFontSize) {
        return 0;
    }

    // Find the middle
    NSInteger fontSize = (minFontSize + maxFontSize) / 2;
    // Create the font
    UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize];
    // Create a constraint size with max height
    CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT);
    // Find label size for current font size
    CGRect rect = [label.text boundingRectWithSize:constraintSize
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                        attributes:@{NSFontAttributeName : font}
                                           context:nil];
    CGSize labelSize = rect.size;

    // EDIT:  The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results.
    if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) {
        return fontSize;
    } else if (labelSize.height > size.height || labelSize.width > size.width) {
        return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size];
    } else {
        return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size];
    }
}

- (void)sizeBinaryLabel:(UILabel *)label toRect:(CGRect)labelRect {

    // Set the frame of the label to the targeted rectangle
    label.frame = labelRect;

    // Try all font sizes from largest to smallest font
    int maxFontSize = 300;
    int minFontSize = 5;

    NSInteger size = [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:maxFontSize withSize:label.frame.size];

    label.font = [UIFont fontWithName:label.font.fontName size:size];

}

Credit goes also to https://gist.github.com/988219

Well answered 2/2, 2013 at 14:12 Comment(10)
+1 - I was just going to write up a binary search solution and scrolled down only to find out a binary solution had already been posted! This definitely is a better solution for most cases. Awesome agarcian.Swagman
Thanks Pavan. This is the beauty of SO. Collaboration will save us hours and hours of development :-)Well
I just added an edit to the answer because with the previous code, some labels that were too thin were not displaying the text completely inside the label. The change in the code includes checking not only for height but for width.Well
Mmm, @Well 's code always yields 0 for me. The maxFontSize and minFontSize become equal without the first if-then ever being reached. Even in a very basic test project. Niels' code works fine, albeit slower.Ejaculatory
It is very unprecise for very long texts and very short texts.Synonym
Albeit this is faster, it's not a reliant solution, though. Does also use APIs which are deprecated as of iOS 7.Poling
However, I've improved the answer to use non-deprecated APIs and to not alter the current used font.Poling
@Leandros: I use this code in a production app on the app store. It works fine. Why do you say is not a reliable solution? It certainly works for my use case which includes short phrases on the labels; namely numbers for callouts over a graphic. What is your use case?Well
@Well I tried your solution. It works but it does not do word wrapping. For example if I have a label text "Internationalisation". My UILabel frame width is 60px and number of lines is 2. Now it should wrap the text in one line with minimum possible font. But it keeps the text in 2 lines with a bit bigger font and the text breaks up like "Internationali" in first line and "sation" in second. How can we apply word wrapping in it. Please respond.Premium
Did you mean to put all ANDs in the first if statement or is an OR missing?Pyrrhotite
C
9

Here's Swift version according to @NielsCastle answer, using binary search

extension UILabel{

    func adjustFontSizeToFitRect(rect : CGRect){

        if text == nil{
            return
        }

        frame = rect

        let maxFontSize: CGFloat = 100.0
        let minFontSize: CGFloat = 5.0

        var q = Int(maxFontSize)
        var p = Int(minFontSize)

        let constraintSize = CGSize(width: rect.width, height: CGFloat.max)

        while(p <= q){
            let currentSize = (p + q) / 2
            font = font.fontWithSize( CGFloat(currentSize) )
            let text = NSAttributedString(string: self.text!, attributes: [NSFontAttributeName:font])
            let textRect = text.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, context: nil)

            let labelSize = textRect.size

            if labelSize.height < frame.height && labelSize.height >= frame.height-10 && labelSize.width < frame.width && labelSize.width >= frame.width-10 {
                break
            }else if labelSize.height > frame.height || labelSize.width > frame.width{
                q = currentSize - 1
            }else{
                p = currentSize + 1
            }
        }

    }
}

Usage

label.adjustFontSizeToFitRect(rect)

often just

label.adjustFontSizeToFitRect(rect.frame)
Chair answered 11/7, 2015 at 16:26 Comment(2)
Nice, thanks :) - I would only suggest replacing this early return using guard statement.Semipro
@Chair works nice. Only part where this is buggy is when u make hight of UILabel considerably high then font size calculation fucks up and it goes beyond width. Will try to solve this bug and post here.Flavor
D
5

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 <UIKit/UIKit.h>

@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];
Debit answered 11/11, 2015 at 18:42 Comment(2)
This. Works. Perfectly.Cilicia
This was very elegant, and works very well. I had one small improvement which allows for the case where the label text is a single word. In this situation you don't want it to be split across lines, so I added: // If there is only one word, then reduce the height so as to force boundingRectWithSize to fit it on // one line. // if ([label.text componentsSeparatedByString:@" "].count == 1) { ` labelSize.height /= 2.0;` } just after the declaration of labelSize.Stronski
I
5

Here's a Swift extension for UILabel. It runs a binary search algorithm to resize the font and bounds of the label, and is tested to work for iOS 12.

USAGE: Resizes the font to fit a size of 100x100 (accurate within 1.0 font point) and aligns it to top.

let adjustedSize = <label>.fitFontForSize(CGSizeMake(100, 100))
<label>.frame = CGRect(x: 0, y: 0, width: 100, height: adjustedSize.height)

Copy/Paste the following into your file:

extension UILabel {
    @discardableResult func fitFontForSize(_ constrainedSize: CGSize,
                                           maxFontSize: CGFloat = 100,
                                           minFontSize: CGFloat = 5,
                                           accuracy: CGFloat = 1) -> CGSize {
        assert(maxFontSize > minFontSize)

        var minFontSize = minFontSize
        var maxFontSize = maxFontSize
        var fittingSize = constrainedSize

        while maxFontSize - minFontSize > accuracy {
            let midFontSize: CGFloat = ((minFontSize + maxFontSize) / 2)
            font = font.withSize(midFontSize)
            fittingSize = sizeThatFits(constrainedSize)
            if fittingSize.height <= constrainedSize.height
                && fittingSize.width <= constrainedSize.width {
                minFontSize = midFontSize
            } else {
                maxFontSize = midFontSize
            }
        }

        return fittingSize
    }
} 

This function will not change the label size, only the font property is affected. You can use the returned size value to adjust the layout of the label.

Imogen answered 31/12, 2015 at 0:25 Comment(2)
Nice extension ;) Keep in mind that var parameter in function declaration is actually deprecated and will be removed in Swift 3.Confiture
I made several improves to this snippet. Now it's updated to modern Swift and works on iOS 12. Also, I replaced sizeToFit with sizeThatFits so calling this method doesn't affect the label frame and works with constraints pretty well. Also, I added the convenient return value, which can be used for further layout adjustments. Some redundant calls where removed.Topheavy
B
2

If someone is looking for a MonoTouch/Xamarin.iOS implementation, as I did ... here you go:

private int BinarySearchForFontSizeForText(NSString text, int minFontSize, int maxFontSize, SizeF size)
{
    if (maxFontSize < minFontSize) 
        return minFontSize;

    int fontSize = (minFontSize + maxFontSize) / 2;
    UIFont font = UIFont.BoldSystemFontOfSize(fontSize);

    var constraintSize = new SizeF(size.Width, float.MaxValue);
    SizeF labelSize = text.StringSize(font, constraintSize, UILineBreakMode.WordWrap);

    if (labelSize.Height >= size.Height + 10 && labelSize.Width >= size.Width + 10 && labelSize.Height <= size.Height && labelSize.Width <= size.Width)
        return fontSize;
    else if (labelSize.Height > size.Height || labelSize.Width > size.Width) 
        return BinarySearchForFontSizeForText(text, minFontSize, fontSize - 1, size);
    else 
        return BinarySearchForFontSizeForText(text, fontSize + 1, maxFontSize, size);
}

private void SizeLabelToRect(UILabel label, RectangleF labelRect)
{
    label.Frame = labelRect;

    int maxFontSize = 300;
    int minFontSize = 5;
    int size = BinarySearchForFontSizeForText(new NSString(label.Text), minFontSize, maxFontSize, label.Frame.Size);

    label.Font = UIFont.SystemFontOfSize(size);
}

It's a translation of agarcian's code from Objective-C to C#, with a small modification: as the returning result has always been 0 (see the comment of borked) I am returning the calculated minFontSize, which results in a correct font size.

Baronet answered 17/8, 2013 at 10:42 Comment(0)
E
1

Also set myLabel.numberOfLines = 10 or to whatever the max number of lines you want.

Ec answered 16/5, 2010 at 19:19 Comment(2)
Or simply 0 for infinite number of linesElitism
^This guy knows what he's talking aboutStoma
A
1

All these are interesting solutions to the original problem, however all of them are also missing an important thing: If you solely rely on the familyName to get the next font to test, you're losing the weight information and possibly more advanced attributes like small caps, figure style, etc.

A better approach is instead of passing the font name around and doing [UIFont fontWithName:someFontName size:someFontSize], passing UIFontDescriptor objects along and then doing [UIFont fontWithDescriptor:someFontDescriptor size:someFontSize].

Augury answered 28/2, 2016 at 19:29 Comment(0)
D
1

Since I didn't find a working solution answering to all my needs using the above answers, I have created my own components providing the following features: FittableFontLabel

  • Adjust font to fit height and width when using multilines label
  • Adjust font to fit width when using single line label, the height label will resize itself
  • Support for NSAttributedStrings as well as basic string
  • Auto adjusting the size when changing a label text / frame
  • ...

If any of you is interesting, it's a full swift library available using CocoaPods: https://github.com/tbaranes/FittableFontLabel

Drake answered 27/3, 2016 at 17:0 Comment(0)
L
0

Niels Castle code work find.

Here is the same idea with a different implementation.
My solution is more precise but also much more CPU intensive.

Add this function to a class who inherit UILabel.

-(void)fitCurrentFrame{

    CGSize iHave = self.frame.size;

    BOOL isContained = NO;
    do{
        CGSize iWant = [self.text sizeWithFont:self.font];
        if(iWant.width > iHave.width || iWant.height > iHave.height){
            self.font = [UIFont fontWithName:self.font.fontName size:self.font.pointSize - 0.1];
            isContained = NO;
        }else{
            isContained = YES;
        }

    }while (isContained == NO);
}
Lipson answered 14/6, 2013 at 10:27 Comment(0)
N
0

I have created Category for UILabel based on @agarcian's answer. But i calculate fontSize depending on square needed on screen for drawing text. This method not need loops and calculating is done by one iteration.

Here the .h file:

//  UILabel+Extended.h
//  Created by Firuz on 16/08/14.
//  Copyright (c) 2014. All rights reserved.

#import <UIKit/UIKit.h>

@interface UILabel (Extended)

/** This method calculate the optimal font size for current number of lines in UILable. Mus be called after drawing UILabel view */
- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize;

@end

And here the .m file:

//  UILabel+Extended.m
//  Created by Firuz on 16/08/14.
//  Copyright (c) 2014. All rights reserved.

#import "UILabel+Extended.h"

@implementation UILabel (Extended)

- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize
{
    if (maxFontSize < minFontSize) {
        return 0;
    }

    UIFont *font = [UIFont fontWithName:self.font.fontName size:maxFontSize];

    CGFloat lineHeight = [font lineHeight];

    CGSize constraintSize = CGSizeMake(MAXFLOAT, lineHeight);

    CGRect rect = [self.text boundingRectWithSize:constraintSize
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                        attributes:@{NSFontAttributeName : font}
                                           context:nil];

    CGFloat labelSqr = self.frame.size.width * self.frame.size.height;
    CGFloat stringSqr = rect.size.width/self.frame.size.width * (lineHeight + font.pointSize) * self.frame.size.width;

    CGFloat multiplyer = labelSqr/stringSqr;

    if (multiplyer < 1) {
        if (minFontSize < maxFontSize*multiplyer) {
            return maxFontSize * multiplyer;
        } else {
            return minFontSize;
        }
    }
    return maxFontSize;
}

@end
Nitrogenize answered 16/8, 2014 at 9:14 Comment(0)
A
0

All binary searches are good, but stop recursion using frame checks not so logically. More nicely check font size, cause UIFont supports float size and this font is more suitable. Plus using label paragraph style to calculate size morу exactly.

If someone interesting, you can look bellow code:

static UIFont * ___suitableFontInRangePrivate(const CGSize labelSize,
                                            NSParagraphStyle * paragraphStyle,
                                            NSString * fontName,
                                            NSString * text,
                                            const CGFloat minSize,
                                            const CGFloat maxSize)
{
    // Font size in range, middle size between max & min.
    const CGFloat currentSize = minSize + ((maxSize - minSize) / 2);

    // Font with middle size.
    UIFont * currentFont = [UIFont fontWithName:fontName size:currentSize];

    // Calculate text height.
    const CGFloat textHeight = [text boundingRectWithSize:CGSizeMake(labelSize.width, CGFLOAT_MAX)
                                                options:NSStringDrawingUsesLineFragmentOrigin
                                            attributes:@{ NSFontAttributeName : currentFont, NSParagraphStyleAttributeName : paragraphStyle }
                                                context:nil].size.height;
    CGFloat min, max;
    if (textHeight > labelSize.height)
    {
        // Take left range part.
        min = minSize;
        max = currentSize;
    }
    else
    {
        // Take right range part.
        min = currentSize;
        max = maxSize;
    }

    // If font size in int range [0.0; 2.0] - got it, othervice continue search.
    return ((max - min) <= 2.0) ? currentFont : ___suitableFontInRangePrivate(labelSize, paragraphStyle, fontName, text, min, max);
}

void UILabelAdjustsFontSizeToFrame(UILabel * label)
{
    if (!label) return;

    NSString * text = [label text];

    __block NSParagraphStyle * style = nil;
    [[label attributedText] enumerateAttributesInRange:NSMakeRange(0, [text length])
                                            options:(NSAttributedStringEnumerationOptions)0
                                            usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop){
                                                id paragraphStyle = [attrs objectForKey:@"NSParagraphStyle"];
                                                if (paragraphStyle) style = [paragraphStyle retain];
                                            }];

    if (!style)
    {
        NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init];
        if (paragraphStyle)
        {
            [paragraphStyle setLineBreakMode:[label lineBreakMode]];
            [paragraphStyle setAlignment:[label textAlignment]];
        }
        style = paragraphStyle;
    }

    UIFont * suitableFont = ___suitableFontInRangePrivate([label frame].size, style, [[label font] fontName], text, 0, 500);
    [label setFont:suitableFont];
    [style release];
}
Adsorbent answered 2/3, 2015 at 14:16 Comment(0)
K
0

Swift 3 "binary search solution" based on this answer with minor improvements. Sample is in context of UITextView subclass:

func binarySearchOptimalFontSize(min: Int, max: Int) -> Int {
    let middleSize = (min + max) / 2

    if min > max {
        return middleSize
    }

    let middleFont = UIFont(name: font!.fontName, size: CGFloat(middleSize))!

    let attributes = [NSFontAttributeName : middleFont]
    let attributedString = NSAttributedString(string: text, attributes: attributes)

    let size = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
    let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
    let textSize = attributedString.boundingRect(with: size, options: options, context: nil)

    if textSize.size.equalTo(bounds.size) {
        return middleSize
    } else if (textSize.height > bounds.size.height || textSize.width > bounds.size.width) {
        return binarySearchOptimalFontSize(min: min, max: middleSize - 1)
    } else {
        return binarySearchOptimalFontSize(min: middleSize + 1, max: max)
    }
}

I hope that helps someone.

Karolyn answered 14/11, 2016 at 23:2 Comment(1)
This does not work as expected. Swap the greatestFiniteMagnitude to the width, like CGSize(width: bounds.width, height: .greatestFiniteMagnitude)Globulin
S
0

@agarcian's answer was close but it didn't quite work for me, as someone else mentioned in a comment, it always returned 0.

Here is my attempt.

Cheers!

/**
 * Returns the font size required in order to fit the specified text in the specified area.
 * NB! When drawing, be sure to pass in the same options that we pass to boundingRectWithSize:options:attributes:context:
 * Heavily modified form of: https://mcmap.net/q/413365/-how-to-adjust-font-size-of-label-to-fit-the-rectangle
 */
+(NSInteger)fontSizeForText:(NSString *)text withFont:(UIFont *)font inArea:(CGSize)areaSize minFontSize:(NSInteger)minFontSize maxFontSize:(NSInteger)maxFontSize
{
// If the sizes are incorrect, return 0, or error, or an assertion.
    if (maxFontSize < minFontSize) {
        return 0;
    }

    // Find the middle
    NSInteger fontSize = (minFontSize + maxFontSize) / 2;
    // Create the font
    UIFont *f = [UIFont fontWithName:font.fontName size:fontSize];
    // Create a constraint size with max height
    CGSize constraintSize = CGSizeMake(areaSize.width, MAXFLOAT);
    // Find label size for current font size
    CGRect rect = [text boundingRectWithSize:constraintSize
                                           options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                        attributes:@{NSFontAttributeName : f}
                                           context:nil];
    CGSize labelSize = rect.size;

    if (labelSize.height <= areaSize.height && labelSize.width  <= areaSize.width )
    {
        return fontSize;
    }
    else if (labelSize.height > areaSize.height || labelSize.width > areaSize.width)
    {
        return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize maxFontSize:maxFontSize -1];;
    }
    else
    {
        return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize+1 maxFontSize:maxFontSize];;
    }
}
Swaggering answered 29/1, 2017 at 8:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.