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 anythin
@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: http://stackoverflow.com/a/14662750/1027452
*/
+(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];;
}
}
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.
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);
}
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
NSAttributedStrings
as well as basic stringIf any of you is interesting, it's a full swift library available using CocoaPods: https://github.com/tbaranes/FittableFontLabel
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.
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