Icaro
Icaro

Reputation: 14845

How to adjust a UILabel font size based on the height available to the label

I am using autolayout and text resizing with UILables and it works fine with width but not so much in the height.

If there is space for the text in the width but not in the height the text does not compress

Any Ideas how can I create constraints to do that? Or it that is not possible how can I find the minimum size of text that fits an rectangle?

Upvotes: 5

Views: 4608

Answers (3)

Edwin Finch
Edwin Finch

Reputation: 903

Here is an Objective-C implementation of tymac's answer for anyone interested.

(I was in a bit of a rush and didn't have time to fix the lower case Gs, Js, Ps and Qs being cut so I quickly subtracted two from the average text size in the calculation function, sorry.)

#import "LabelWithAdaptiveTextHeight.h"

#define DISPLAY_FONT_MINIMUM 6
#define DISPLAY_FONT_MAXIMUM 50

@interface LabelWithAdaptiveTextHeight ()

@end

@implementation LabelWithAdaptiveTextHeight

- (UIFont*)fontToFitHeight {
    float minimumFontSize = DISPLAY_FONT_MINIMUM;
    float maximumFontSize = DISPLAY_FONT_MAXIMUM;
    float fontSizeAverage = 0;
    float textAndLabelHeightDifference = 0;

    while(minimumFontSize <= maximumFontSize){
        fontSizeAverage = minimumFontSize + (maximumFontSize - minimumFontSize) / 2;
        if(self.text){
            float labelHeight = self.frame.size.height;
            float testStringHeight = [self.text sizeWithAttributes:@{
                                                                NSFontAttributeName: [self.font fontWithSize:fontSizeAverage]
                                                                }].height;

            textAndLabelHeightDifference = labelHeight - testStringHeight;

            if(fontSizeAverage == minimumFontSize || fontSizeAverage == maximumFontSize){
                return [self.font fontWithSize:fontSizeAverage- (textAndLabelHeightDifference < 0)];
            }
            if(textAndLabelHeightDifference < 0){
                maximumFontSize = fontSizeAverage - 1;
            }
            else if(textAndLabelHeightDifference > 0){
                minimumFontSize = fontSizeAverage + 1;
            }
            else{
                return [self.font fontWithSize:fontSizeAverage];
            }
        }
        else {
            break; //Prevent infinite loop
        }
    }
    return [self.font fontWithSize:fontSizeAverage-2];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    self.font = [self fontToFitHeight];
}

@end

Upvotes: 1

Kunal Verma
Kunal Verma

Reputation: 602

Swift 3.0 for anyone new coming here.

Thanks a lot @tymac for the original answer.

Really helped me not to worry about text sizes when making my apps universal.

import UIKit
import Foundation

class LabelWithAdaptiveTextHeight: UILabel {

    override func layoutSubviews() {
        super.layoutSubviews()
        font = fontToFitHeight()
    }

    // Returns an UIFont that fits the new label's height.
    private func fontToFitHeight() -> UIFont {

        var minFontSize: CGFloat = 20
        var maxFontSize: CGFloat = 250
        var fontSizeAverage: CGFloat = 0
        var textAndLabelHeightDiff: CGFloat = 0

        while (minFontSize <= maxFontSize) {
            fontSizeAverage = minFontSize + (maxFontSize - minFontSize) / 2

            if let labelText: String = text {
                let labelHeight = frame.size.height

                let testStringHeight = labelText.size(attributes: [NSFontAttributeName: font.withSize(fontSizeAverage)]).height

                textAndLabelHeightDiff = labelHeight - testStringHeight

                if (fontSizeAverage == minFontSize || fontSizeAverage == maxFontSize) {
                    if (textAndLabelHeightDiff < 0) {
                        return font.withSize(fontSizeAverage - 1)
                    }
                    return font.withSize(fontSizeAverage)
                }

                if (textAndLabelHeightDiff < 0) {
                    maxFontSize = fontSizeAverage - 1

                } else if (textAndLabelHeightDiff > 0) {
                    minFontSize = fontSizeAverage + 1

                } else {
                    return font.withSize(fontSizeAverage)
                }
            }
        }
        return font.withSize(fontSizeAverage)
    }
}

Upvotes: 6

Edison
Edison

Reputation: 11987

You can set the font to automatically fill the size of a label, and optionally not go below a minimum font size. Just set adjustsFontSizeToFitWidth to YES. Check out the UILabel Class Reference if you need more information.

Although the boolean is called "adjustsFontSizeToWidth," it really means the largest size for the height of the label, that will stay on one line of the label (or however many lines you specify).

Subclassed UILabel and overrode layoutSubviews. Then each time the UILabel gets its size changed, the font size is recalculated:

import Foundation
import UIKit

class LabelWithAdaptiveTextHeight: UILabel {

override func layoutSubviews() {
    super.layoutSubviews()
    font = fontToFitHeight()
}

// Returns an UIFont that fits the new label's height.
private func fontToFitHeight() -> UIFont {

    var minFontSize: CGFloat = DISPLAY_FONT_MINIMUM // CGFloat 18
    var maxFontSize: CGFloat = DISPLAY_FONT_BIG     // CGFloat 67
    var fontSizeAverage: CGFloat = 0
    var textAndLabelHeightDiff: CGFloat = 0

    while (minFontSize <= maxFontSize) {
        fontSizeAverage = minFontSize + (maxFontSize - minFontSize) / 2

        if let labelText: NSString = text {
            let labelHeight = frame.size.height

            let testStringHeight = labelText.sizeWithAttributes(
                [NSFontAttributeName: font.fontWithSize(fontSizeAverage)]
            ).height

            textAndLabelHeightDiff = labelHeight - testStringHeight

            if (fontSizeAverage == minFontSize || fontSizeAverage == maxFontSize) {
                if (textAndLabelHeightDiff < 0) {
                    return font.fontWithSize(fontSizeAverage - 1)
                }
                return font.fontWithSize(fontSizeAverage)
            }

            if (textAndLabelHeightDiff < 0) {
                maxFontSize = fontSizeAverage - 1

            } else if (textAndLabelHeightDiff > 0) {
                minFontSize = fontSizeAverage + 1

            } else {
                return font.fontWithSize(fontSizeAverage)
            }
        }
    }
    return font.fontWithSize(fontSizeAverage)
}
}

Upvotes: 8

Related Questions