Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 918
Icaro Avatar asked Aug 02 '15 02:08

Icaro


3 Answers

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)
}
}
like image 143
Edison Avatar answered Oct 20 '22 21:10

Edison


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)
    }
}
like image 22
Kunal Verma Avatar answered Oct 20 '22 22:10

Kunal Verma


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
like image 1
Edwin Finch Avatar answered Oct 20 '22 22:10

Edwin Finch