I have a multiline UILabel whose font size I\'d like to adjust depending on the text length. The whole text should fit into the label\'s frame without truncating it.
It seems to me that there is now a better answer to this question
I have discovered that the font size adjusts automatically on a multiline label when you set adjustsFontSizeToFitWidth true, numberOfLines 0, a minimumScaleFactor little enough to let the font shrink as needed, and the default lineBreakMode (byTruncatingTail)
PS: I cannot find anything about this change on the official documentation. I created a question to find out more informations on this topic here
In this question, 0x90 provides a solution that - although a bit ugly - does what I want. Specifically, it deals correctly with the situation that a single word does not fit the width at the initial font size. I've slightly modified the code so that it works as a category on NSString
:
- (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
CGFloat fontSize = [font pointSize];
CGFloat height = [self sizeWithFont:font constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
UIFont *newFont = font;
//Reduce font size while too large, break if no height (empty string)
while (height > size.height && height != 0) {
fontSize--;
newFont = [UIFont fontWithName:font.fontName size:fontSize];
height = [self sizeWithFont:newFont constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
};
// Loop through words in string and resize to fit
for (NSString *word in [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
CGFloat width = [word sizeWithFont:newFont].width;
while (width > size.width && width != 0) {
fontSize--;
newFont = [UIFont fontWithName:font.fontName size:fontSize];
width = [word sizeWithFont:newFont].width;
}
}
return fontSize;
}
To use it with a UILabel
:
CGFloat fontSize = [label.text fontSizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:label.frame.size];
label.font = [UIFont boldSystemFontOfSize:fontSize];
EDIT: Fixed the code to initialize newFont
with font
. Fixes a crash under certain circumstances.
For a fully working solution, see the bottom of my answer
In some cases, changing "Line Breaks" from "Word Wrap" to "Truncate Tail" may be all you need, if you know how many lines you want (e.g. "2"): Credit: Becky Hansmeyer
There is an ObjC extension provided in comments, that calculate fontsize required to fit multiline text into UILabel. It was rewritten in Swift (since it is 2016):
//
// NSString+KBAdditions.swift
//
// Created by Alexander Mayatsky on 16/03/16.
//
// Original code from http://stackoverflow.com/a/4383281/463892 & http://stackoverflow.com/a/18951386
//
import Foundation
import UIKit
protocol NSStringKBAdditions {
func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat
}
extension NSString : NSStringKBAdditions {
func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat {
var fontSize = font.pointSize
let minimumFontSize = fontSize * minimumScaleFactor
var attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: font])
var height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height
var newFont = font
//Reduce font size while too large, break if no height (empty string)
while (height > size.height && height != 0 && fontSize > minimumFontSize) {
fontSize--;
newFont = UIFont(name: font.fontName, size: fontSize)!
attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: newFont])
height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height
}
// Loop through words in string and resize to fit
for word in self.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) {
var width = word.sizeWithAttributes([NSFontAttributeName:newFont]).width
while (width > size.width && width != 0 && fontSize > minimumFontSize) {
fontSize--
newFont = UIFont(name: font.fontName, size: fontSize)!
width = word.sizeWithAttributes([NSFontAttributeName:newFont]).width
}
}
return fontSize;
}
}
Link to full code: https://gist.github.com/amayatsky/e6125a2288cc2e4f1bbf
Thanks, with that and a little more from someone else I did this custom UILabel, that will respect the minimum font size and there's a bonus option to align the text to top.
h:
@interface EPCLabel : UILabel {
float originalPointSize;
CGSize originalSize;
}
@property (nonatomic, readwrite) BOOL alignTextOnTop;
@end
m:
#import "EPCLabel.h"
@implementation EPCLabel
@synthesize alignTextOnTop;
-(void)verticalAlignTop {
CGSize maximumSize = originalSize;
NSString *dateString = self.text;
UIFont *dateFont = self.font;
CGSize dateStringSize = [dateString sizeWithFont:dateFont
constrainedToSize:CGSizeMake(self.frame.size.width, maximumSize.height)
lineBreakMode:self.lineBreakMode];
CGRect dateFrame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, dateStringSize.height);
self.frame = dateFrame;
}
- (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
CGFloat fontSize = [font pointSize];
CGFloat height = [self.text sizeWithFont:font
constrainedToSize:CGSizeMake(size.width,FLT_MAX)
lineBreakMode:UILineBreakModeWordWrap].height;
UIFont *newFont = font;
//Reduce font size while too large, break if no height (empty string)
while (height > size.height && height != 0 && fontSize > self.minimumFontSize) {
fontSize--;
newFont = [UIFont fontWithName:font.fontName size:fontSize];
height = [self.text sizeWithFont:newFont
constrainedToSize:CGSizeMake(size.width,FLT_MAX)
lineBreakMode:UILineBreakModeWordWrap].height;
};
// Loop through words in string and resize to fit
if (fontSize > self.minimumFontSize) {
for (NSString *word in [self.text componentsSeparatedByString:@" "]) {
CGFloat width = [word sizeWithFont:newFont].width;
while (width > size.width && width != 0 && fontSize > self.minimumFontSize) {
fontSize--;
newFont = [UIFont fontWithName:font.fontName size:fontSize];
width = [word sizeWithFont:newFont].width;
}
}
}
return fontSize;
}
-(void)setText:(NSString *)text {
[super setText:text];
if (originalSize.height == 0) {
originalPointSize = self.font.pointSize;
originalSize = self.frame.size;
}
if (self.adjustsFontSizeToFitWidth && self.numberOfLines > 1) {
UIFont *origFont = [UIFont fontWithName:self.font.fontName size:originalPointSize];
self.font = [UIFont fontWithName:origFont.fontName size:[self fontSizeWithFont:origFont constrainedToSize:originalSize]];
}
if (self.alignTextOnTop) [self verticalAlignTop];
}
-(void)setAlignTextOnTop:(BOOL)flag {
alignTextOnTop = YES;
if (alignTextOnTop && self.text != nil)
[self verticalAlignTop];
}
@end
I hope it helps.