Calculating UILabel Text Size - ios

I am drawing UILabels programmatically. They get their sizes from a database. So I cannot just use sizeToFit. I have already implemented a function that redraws UILabels with a passed ratio. So all I need to find is the text in UILabel from my view that would require the maximum ratio to redraw UILabels.
So finally I need to do something like this:
double ratio = 1.00;
for (UILabel* labels in sec.subviews) {
float widthLabel = labels.frame.size.width;
float heightLabel = labels.frame.size.height;
float heightText = //get the text height here
float widthText = //get the text width here
if (widthLabel < widthText) {
ratio = MAX(widthText/widthLabel,ratio);
}
if (heightLabel < heightText) {
ratio = MAX(heightText/heightLabel, ratio);
}
}
//redraw UILabels with the given ratio here
So how can I get the height and width size of a text, as some of my text do not fit into the label I cannot simply use label bounds? I am using Xcode 5 and iOS 7.

All of the [NSString sizeWithFont...] methods are deprecated in iOS 7. Use this instead.
CGRect labelRect = [text
boundingRectWithSize:labelSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : [UIFont systemFontOfSize:14]
}
context:nil];
Also see https://developer.apple.com/documentation/foundation/nsstring/1619914-sizewithfont.
UPDATE - example of boundingRectWithSize output
Per your comment I did a simple test. The code and output is below.
// code to generate a bounding rect for text at various font sizes
NSString *text = #"This is a long sentence. Wonder how much space is needed?";
for (NSNumber *n in #[#(12.0f), #(14.0f), #(18.0f)]) {
CGFloat fontSize = [n floatValue];
CGRect r = [text boundingRectWithSize:CGSizeMake(200, 0)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]}
context:nil];
NSLog(#"fontSize = %f\tbounds = (%f x %f)",
fontSize,
r.size.width,
r.size.height);
}
this produces the following output (note that the bounds change as expected as the font size gets larger):
fontSize = 12.000000 bounds = (181.152008 x 28.632000)
fontSize = 14.000000 bounds = (182.251999 x 50.105999)
fontSize = 18.000000 bounds = (194.039993 x 64.421997)

Length gets the number of characters. If you want to get the width of the text:
Objective-C
CGSize textSize = [label.text sizeWithAttributes:#{NSFontAttributeName:[label font]}];
Swift 4
let size = label.text?.size(withAttributes: [.font: label.font]) ?? .zero
This gets you the size. And you can compare the textSize.width of each label.

Another simple way to do this that I haven't seen mentioned yet:
CGSize textSize = [label intrinsicContentSize];
(This only works correctly after you have set the label's text and font, of course.)

Here is a swift variant.
let font = UIFont(name: "HelveticaNeue", size: 25)!
let text = "This is some really long text just to test how it works for calculating heights in swift of string sizes. What if I add a couple lines of text?"
let textString = text as NSString
let textAttributes = [NSFontAttributeName: font]
textString.boundingRectWithSize(CGSizeMake(320, 2000), options: .UsesLineFragmentOrigin, attributes: textAttributes, context: nil)

Little advice guys, if like me you're using, boundingRectWithSize with [UIFont systemFontOFSize:14]
If your string is two lines long, the returned rect height is something like 33,4 points.
Don't make the mistake, like me, to cast it into an int, because 33,4 becomes 33, and 33 points height label pass from two to one line!

The problem with
CGRect r = [text boundingRectWithSize:CGSizeMake(200, 0)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]}
context:nil];
is boundingRectWithSize which determines the maximum value that CGRect can have.
My solution for this problem is to check if it exceeds, if not then text can fit into the label. I did it by using loops.
NSString *text = #"This is a long sentence. Wonder how much space is needed?";
CGFloat width = 100;
CGFloat height = 100;
bool sizeFound = false;
while (!sizeFound) {
NSLog(#"Begin loop");
CGFloat fontSize = 14;
CGFloat previousSize = 0.0;
CGFloat currSize = 0.0;
for (float fSize = fontSize; fSize < fontSize+6; fSize++) {
CGRect r = [text boundingRectWithSize:CGSizeMake(width, height)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:fSize]}
context:nil];
currSize =r.size.width*r.size.height;
if (previousSize >= currSize) {
width = width*11/10;
height = height*11/10;
fSize = fontSize+10;
}
else {
previousSize = currSize;
}
NSLog(#"fontSize = %f\tbounds = (%f x %f) = %f",
fSize,
r.size.width,
r.size.height,r.size.width*r.size.height);
}
if (previousSize == currSize) {
sizeFound = true;
}
}
NSLog(#"Size found with width %f and height %f", width, height);
After each iteration the size of height and width increments 10% of its value.
The reason why I picked 6 is because I did not want the label to be too squishy.
For a solution that does not use loops:
NSString *text = #"This is a long sentence. Wonder how much space is needed?";
CGFloat width = 100;
CGFloat height = 100;
CGFloat currentFontSize = 12;
CGRect r1 = [text boundingRectWithSize:CGSizeMake(width, height)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:currentFontSize+6]}
context:nil];
CGRect r2 = [text boundingRectWithSize:CGSizeMake(width, height)
options:NSStringDrawingUsesFontLeading
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:currentFontSize+6]}
context:nil];
CGFloat firstVal =r1.size.width*r1.size.height;
CGFloat secondVal =r2.size.width*r2.size.height;
NSLog(#"First val %f and second val is %f", firstVal, secondVal);
if (secondVal > firstVal) {
float initRat = secondVal/firstVal;
float ratioToBeMult = sqrtf(initRat);
width *= ratioToBeMult;
height *= ratioToBeMult;
}
NSLog(#"Final width %f and height %f", width, height);
//for verifying
for (NSNumber *n in #[#(12.0f), #(14.0f), #(17.0f)]) {
CGFloat fontSize = [n floatValue];
CGRect r = [text boundingRectWithSize:CGSizeMake(width, height)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]}
context:nil];
NSLog(#"fontSize = %f\tbounds = (%f x %f) = %f",
fontSize,
r.size.width,
r.size.height,r.size.width*r.size.height);
firstVal =r.size.width*r.size.height;
}
Where the last loop is proof that larger font can give a higher size result.

A solution that works with multiline labels (Swift 4), to calculate the height from a fixed width:
let label = UILabel(frame: .zero)
label.numberOfLines = 0 // multiline
label.font = UIFont.systemFont(ofSize: UIFont.labelFontSize) // your font
label.preferredMaxLayoutWidth = width // max width
label.text = "This is a sample text.\nWith a second line!" // the text to display in the label
let height = label.intrinsicContentSize.height

By using this line of code we can get the size of text on the label.
let str = "Sample text"
let size = str.sizeWithAttributes([NSFontAttributeName:UIFont.systemFontOfSize(17.0)])
So, we can use the both width and height.

msgStr string get size :
let msgStr:NSString = Data["msg"]! as NSString
let messageSize = msgStr.boundingRect(with: CGSize(width: ChatTable.frame.width-116, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName:UIFont(name: "Montserrat-Light", size: 14)!], context: nil).size

Swift 3.0
func getLabelHeight() -> CGFloat {
let font = UIFont(name: "OpenSans", size: 15)!
let textString = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." as NSString
let textAttributes = [NSFontAttributeName: font]
let rect = textString.boundingRect(with: CGSize(width: 320, height: 2000), options: .usesLineFragmentOrigin, attributes: textAttributes, context: nil)
return rect.size.height
}

It's a really ugly mess given that if you set UILabel font after you have set it with attributedString it clobbers the font info in attributed text and you have to compute based on text+font attributes
Something to the tune of
CGFloat promptLabelMaxWidth = self.promptLabel.frame.size.width;
NSAttributedString *attributedText = self.promptLabel.attributedText;
assert(attributedText);
CGRect rect = [attributedText boundingRectWithSize:(CGSize){promptLabelMaxWidth, CGFLOAT_MAX} options: NSStringDrawingUsesLineFragmentOrigin context:nil];
NSString *text = self.promptLabel.text;
UIFont *font = self.promptLabel.font;
if (font) {
CGRect r = [text boundingRectWithSize: CGSizeMake(promptLabelMaxWidth, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName: font}
context:nil];
if (r.size.height > rect.size.height) {
rect = r;
}
}

Swift 5:
func getTextBounds(_ label : UILabel) -> CGRect {
if label.text != nil && label.font != nil {
return label.text!.boundingRect(
with: CGSize(width: 450, height: 44),
options: [],
attributes: [NSAttributedString.Key.font : label.font!],
context: nil)
}
return CGRect.null
}
func getTextBounds(_ textField : UITextField) -> CGRect {
if textField.text != nil && textField.font != nil {
return textField.text!.boundingRect(
with: CGSize(width: 450, height: 44),
options: [],
attributes: [NSAttributedString.Key.font : textField.font!],
context: nil)
}
return CGRect.null
}
Or, as extensions:
extension UILabel {
func textBounds() -> CGRect {
if self.text != nil && self.font != nil {
return self.text!.boundingRect(
with: CGSize(width: 450, height: 44),
options: [],
attributes: [NSAttributedString.Key.font : self.font!],
context: nil)
}
return CGRect.null
}
}
extension UITextField {
func textBounds() -> CGRect {
if self.text != nil && self.font != nil {
return self.text!.boundingRect(
with: CGSize(width: 450, height: 44),
options: [],
attributes: [NSAttributedString.Key.font : self.font!],
context: nil)
}
return CGRect.null
}
}

Related

Drawing UILabel according to the string length

I am drawing label using drawRect and the code looks like something below.
if (productName && productName.length > 0) {
UILabel *productNameLabel = [[UILabel alloc]init];
productNameLabel.numberOfLines = 2;
productNameLabel.attributedText = [self shadowedTextWithString:productName fontName:#"ProximaNovaA-Light" fontSize:productNameLabelFontSize isOfferType:NO];
[productNameLabel sizeToFit];
//drawing the UILabel
[productNameLabel drawTextInRect:CGRectMake(25, labelYPosition, productNameLabel.frame.size.width, productNameLabel.frame.size.height)];
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 25, labelYPosition);
[productNameLabel.layer renderInContext:UIGraphicsGetCurrentContext()];
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -25, -labelYPosition);
labelYPosition += productNameLabel.frame.origin.y + productNameLabel.frame.size.height+20;
}
However, the productNameLabel.numberOfLiness = 2 doesn't seem to work at all... If the string has length that exceeds the width of the screen, the text is truncated and the UILabel stays one liner.
Anyone knows how do i do it, so that if the length of the string exceeds the width of screen, the exceeded words will go to the second line?
Thanks!
updated code, still doesn't work !
if (productName && productName.length > 0) {
UILabel *productNameLabel = [[UILabel alloc]init];
productNameLabel.lineBreakMode = YES;
productNameLabel.numberOfLines = 0;
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineBreakMode = NSLineBreakByTruncatingTail;
NSMutableAttributedString *productNameAttributedString = [self shadowedTextWithString:productName fontName:#"ProximaNovaA-Light" fontSize:productNameLabelFontSize isOfferType:NO];
[productNameAttributedString addAttribute:NSParagraphStyleAttributeName
value:style
range:NSMakeRange(0, productNameAttributedString.length)];
productNameLabel.attributedText = productNameAttributedString;
CGSize constrainedSize = CGSizeMake(paramImageView.image.size.width -50 , 9999);
CGRect requiredHeight = [productNameLabel.attributedText boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];
if (requiredHeight.size.width > productNameLabel.frame.size.width) {
requiredHeight = CGRectMake(25,labelYPosition, productNameLabel.frame.size.width, requiredHeight.size.height);
}
CGRect newFrame = productNameLabel.frame;
newFrame.size.height = requiredHeight.size.height;
productNameLabel.frame = newFrame;
productNameLabel.backgroundColor = [UIColor redColor];
[productNameLabel drawTextInRect:CGRectMake(25, labelYPosition, paramImageView.image.size.width-50, requiredHeight.size.height)];
//[productNameLabel drawTextInRect:CGRectMake(25, labelYPosition, 30, productNameLabel.frame.size.height)];
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 25, labelYPosition);
[productNameLabel.layer renderInContext:UIGraphicsGetCurrentContext()];
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -25, -labelYPosition);
labelYPosition += productNameLabel.frame.origin.y + productNameLabel.frame.size.height+20;
}
Objective-c
CGSize sizeToFit = [title sizeWithFont:productNameLabel.font constrainedToSize:productNameLabel.frame.size lineBreakMode:productNameLabel.lineBreakMode];
Swift 2.2
var sizeToFit = title.sizeWithFont(productNameLabel.font, constrainedToSize: productNameLabel.frame.size, lineBreakMode: productNameLabel.lineBreakMode)
Swift3.0
var sizeToFit: CGSize = title.size(with: productNameLabel.font, constrainedTo: productNameLabel.frame.size, lineBreakMode: productNameLabel.lineBreakMode)
CGSize constrainedSize = CGSizeMake(self.resizableLable.frame.size.width , 9999);
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:#"HelveticaNeue" size:11.0], NSFontAttributeName,
nil];
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:#"textToShow" attributes:attributesDictionary];
CGRect requiredHeight = [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];
if (requiredHeight.size.width > self.resizableLable.frame.size.width) {
requiredHeight = CGRectMake(0,0, self.resizableLable.frame.size.width, requiredHeight.size.height);
}
CGRect newFrame = self.resizableLable.frame;
newFrame.size.height = requiredHeight.size.height;
self.resizableLable.frame = newFrame;
Implement this method to find width of string pass font as an argument you want to give to string and this method will return width of string.
func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSFontAttributeName: font]
let size = self.size(attributes: fontAttributes)
return size.width
}
Set this width as a width of the UILabel
Swift-3:
Returns the height of label with padding depending on the text.
func heightForView(text: String, font: UIFont, width: CGFloat) -> CGFloat {
let label = UILabel(frame: CGRect.init(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height + labelHeightPadding ///extra padding; if needed be.
}

How do I calculate the UILabel height dynamically [duplicate]

This question already has answers here:
How to calculate UILabel height dynamically?
(14 answers)
Closed 5 years ago.
I have the following code:
label.numberOfLines = 0; // allows label to have as many lines as needed
label.text = #"some long text";
[label sizeToFit];
How do I get the height of label in points?
The easiest way to get the height is sizeThatFits. Use it like this:
Objective-C
CGFloat maxLabelWidth = 100;
CGSize neededSize = [label sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
Swift 3.0
let maxLabelWidth: CGFloat = 100
let neededSize = label.sizeThatFits(CGSize(width: maxLabelWidth, height: CGFloat.greatestFiniteMagnitude))
The height your label needs is neededSize.height.
Note that im using CGFLOAT_MAX for the size height, to make sure the label has enough place to fit in the CGSize.
The height of your label also depends on your width of the label, thats why I added maxLabelWidth, it makes a difference if the label can be 100pt wide or 200pt.
Hope this helps!
Edit:
Make sure you set label.numberOfLines = 0; otherwise neededSize returns the size where the text is in a single line.
Edit:
Added Swift version, although the naming is a bit weird, greatestFiniteMagnitude seems to be the correct equivalent for CGFLOAT_MAX.
Use following method to calculate dynamic UILabel height:
- (CGFloat)getLabelHeight:(UILabel*)label
{
CGSize constraint = CGSizeMake(label.frame.size.width, CGFLOAT_MAX);
CGSize size;
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [label.text boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:label.font}
context:context].size;
size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
return size.height;
}
Just use [label sizeThatFits:label.frame.size]; and it will return the size of label which will fit for given text. Or you can also follow the question
For those who want to estimate the size a label takes in a method which estimates header / cell height in UICollectionView or UITableView, follow this:
Set maxWidth that your label will take
Create a new UILabel and set numberOfLines to 0
Add Font attributes like custom font name and font size if using custom fonts
Set text for this label and get estimated height using sizeThatFits. Label height is neededHeight.height
Swift Version
let maxLabelWidth:CGFloat = collectionView.frame.width - 20
let label = UILabel()
label.numberOfLines = 0
let addressFont = [ NSFontAttributeName: UIFont(name: "OpenSans", size: 12.0)! ]
let addr = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
label.attributedText = NSMutableAttributedString(string: addr , attributes: addressFont )
let neededSize:CGSize = label.sizeThatFits(CGSizeMake(maxLabelWidth, CGFloat.max))
let labelHeight = neededSize.height
Thanks to #FabioBerger
I edited Salman Zaidi's answer a little to make it work better for myself. It works well if you don't have direct access to a label, like when you are trying to get label height in heightForRowAtIndexPath:
-(CGFloat)getLabelHeight:(CGSize)labelSize string: (NSString *)string font: (UIFont *)font{
CGSize size;
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
CGSize boundingBox = [string boundingRectWithSize:labelSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:context].size;
size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
return size.height;
}
You can Create Label dynamically :
-(CGRect)newLableSize:(NSString *)lableString
{
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
CGFloat tempwidth = YourStaticLabelWidth * ScreenWidth / 320;
NSMutableArray *array=[[NSMutableArray alloc]initWithObjects: lableString,nil];
CGRect newLabelsize = [[array objectAtIndex:0] boundingRectWithSize:CGSizeMake(tempwidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:selectFont,NSParagraphStyleAttributeName:paragraphStyle} context:nil];
NSLog(#"New Label Size Width : %f",newLabelsize.size.width);
NSLog(#"New Label Size Height : %f",newLabelsize.size.height);
return newLabelsize;
}
I'm adjust height, with 2 lines in my label
lblUserQuestion.preferredMaxLayoutWidth = 100.0f;
100.0f, it's a size i wanted, and another line,
[lblUserQuestion sizeToFit];
My method complete is,
UILabel *lblUserQuestion = [[UILabel alloc] initWithFrame:CGRectMake(61, 25, self.frame.size.width-61-20, 37.0f)];
lblUserQuestion.numberOfLines= 0;
lblUserQuestion.font =[UIFont fontWithName:#"HelveticaNeue-Thin" size:14.];
lblUserQuestion.adjustsFontSizeToFitWidth = YES;
lblUserQuestion.minimumScaleFactor = 0.5;
lblUserQuestion.preferredMaxLayoutWidth= 100.0f;
lblUserQuestion.text = _photoToVote.label;
- (CGFloat)getTextHeightByWidth:(NSString*)text textFont:(UIFont*)textFont textWidth:(float)textWidth {
if (!text) {
return 0;
}
CGSize boundingSize = CGSizeMake(textWidth, CGFLOAT_MAX);
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:#{ NSFontAttributeName: textFont }];
CGRect rect = [attributedText boundingRectWithSize:boundingSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];
CGSize requiredSize = rect.size;
return requiredSize.height;
}
- (CGFloat)getTextWidthByHeight:(NSString*)text textFont:(UIFont*)textFont textHeight:(float)textHeight {
if (!text) {
return 0.0f;
}
CGSize boundingSize = CGSizeMake(CGFLOAT_MAX, textHeight);
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text
attributes:#{ NSFontAttributeName: textFont }];
CGRect rect = [attributedText boundingRectWithSize:boundingSize
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize requiredSize = rect.size;
return requiredSize.width;
}
Use this function
+ (CGFloat)heightForText:(NSString*)text font:(UIFont*)font withinWidth:(CGFloat)width {
CGSize constraint = CGSizeMake(width, 20000.0f);
CGSize size;
CGSize boundingBox = [text boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil].size;
size = CGSizeMake(ceil(boundingBox.width), ceil(boundingBox.height));
return size.height;
}
- (CGFloat)getLabelsize:(UILabel *)label
{
CGSize maxSize = CGSizeMake(label.frame.size.width, 9999);
CGSize requiredSize = [label sizeThatFits:maxSize];
return requiredSize.height;
}

boundingRectWithSize not replicating UITextView

My requirement in a project is that the font size of the UITextView should decrease according the content of the UITextView. So i am trying to do estimate the size of the text using boundingRectWithSize.
The problem is that the font size I get is a bit too big and some part of the text does get clipped.
My Function :
-(BOOL)updateTextViewFontSizeForText:(NSString*)text{
float fontSize = self.maximumFontSizeInPoints;
self.font = [self.font fontWithSize:fontSize];
CGSize tallerSize ;
CGSize stringSize ;
do
{
if (fontSize <= self.minimumFontSizeInPoints) // it just won't fit
return NO;
fontSize -= 1.0;
self.font = [self.font fontWithSize:fontSize];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
NSDictionary *attributes = #{ NSFontAttributeName: self.font, NSParagraphStyleAttributeName : paragraphStyle };
tallerSize = CGSizeMake(self.frame.size.width,self.frame.size.height-16);// the 16 is given because uitextview adds some offset
stringSize = [text boundingRectWithSize:CGSizeMake(self.contentSize.width,CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attributes context:nil].size;
}while(stringSize.height >= tallerSize.height);
if ([self.onTextChangDelegate respondsToSelector:#selector(onTextChangDelegate)]) {
[self.onTextChangDelegate onTextChanged:text];
}
return YES;
}
I ran into the same issue when trying to do the same thing.
The issue is how UITextView run's its line-breaks compared to boundingRectWithSize. You can read more details here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Concepts/CalcTextLayout.html
But you can actually calculate the exact size! There are basically two properties of a UITextView that you'll need to take into account in order to get correct size estimates. The first is textContainer.lineFragmentPadding, the second is textContainerInset.
First, textContainer.lineFragmentPadding: You may have noticed that your sizing is generally always off by 10px, this is because the systems default value is 5px. When you're calculating your estimated size, you'll need to subtract this value from the size you're checking against and add it back when you have your final value.
Second, textContainerInset. This is a UIEdgeInset that you'll need to add back to your final calculated value to match the systems.
This is code based on how I solved the issue:
- (CGSize)sizeThatFits:(CGSize)size
CGFloat lineFragmentPaddings = self.textContainer.lineFragmentPadding * 2;
CGFloat horzPadding = self.textContainerInset.left + self.textContainerInset.right + lineFragmentPaddings;
CGFloat vertPadding = self.textContainerInset.top + self.textContainerInset.bottom;
size.width -= horzPadding;
CGRect boundingRect = [attributedText boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin context:nil];
size = boundingRect.size;
// I found through debugging that adding 0.25 rounded
// matches sizeThatFits: identically. Not sure why…
size.width += horzPadding + 0.25;
size.height += vertPadding + 0.25;
size = CGSizeRound(size);
return size;
}
Note, CGSizeRound is just a custom function I wrote that rounds the width and height of the CGSize to the nearest 0.5.
For comparison, if you create a second UITextView, and make sure the textContainer.lineFragmentPadding and textContainerInset are the same, you should see the values almost identical to the nearest 0.5.
And to your question about calculating a proper pointSize, this is some pseudo code for that:
CGFloat pointSize = 64;
CGFloat minPointSize = 32;
CGFloat decrementor = 4;
CGFloat padding = self.textContainerInset.left + self.textContainerInset.right + lineFragmentPaddings;
CGFloat actualWidth = self.maxTextViewSize.width - padding * 2;
CGRect boundingRect = CGRectZero;
BOOL isValidPointSize = NO;
do {
if (pointSize < minPointSize) {
pointSize = minPointSize;
boundingRect.size.height = self.maxTextViewSize.height;
isValidPointSize = YES;
} else {
NSDictionary *defaultAttributes = [self.customTextStorage defaultAttributesForPointSize:pointSize];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:defaultAttributes];
boundingRect = [attrString boundingRectWithSize:CGSizeMake(actualWidth, 1024) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
// is the height to big?
if (boundingRect.size.height > self.maxTextViewSize.height) {
// reduce the point size for next iteration of loop
pointSize -= decrementor;
}
// passes height test
else {
isValidPointSize = YES;
}
}
} while (!isValidPointSize);
return pointSize;
Again, the above is pseudo code based on my implementation (not meant for just drop in replacement for what you have). Hope this helps!
try like this
UITextView *textViewObj;//initialise textview.
textViewObj.autoresizesSubviews = NO;
textViewObj.autoresizingMask = UIViewAutoresizingNone;
This is really working in swift,get original height of textview.. try this
let
size = cellQueue.contentLbl.sizeThatFits(CGSizeMake(cellQueue.contentLbl.frame.size.width,CGFloat(MAXFLOAT))) cellQueue.heightConstraintContentLbl.constant = size.height

Calculate Font Size to Fit Frame - Core Text - NSAttributedString - iOS

I have some text which I am drawing into a fixed frame via an NSAttributedString (code below). At the moment I am hard coding the text size to 16. My question is, is there a way to calculate the best fit size for the text for the given frame ?
- (void)drawText:(CGContextRef)contextP startX:(float)x startY:(float)
y withText:(NSString *)standString
{
CGContextTranslateCTM(contextP, 0, (bottom-top)*2);
CGContextScaleCTM(contextP, 1.0, -1.0);
CGRect frameText = CGRectMake(1, 0, (right-left)*2, (bottom-top)*2);
NSMutableAttributedString * attrString = [[NSMutableAttributedString alloc] initWithString:standString];
[attrString addAttribute:NSFontAttributeName
value:[UIFont fontWithName:#"Helvetica-Bold" size:16.0]
range:NSMakeRange(0, attrString.length)];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrString));
struct CGPath * p = CGPathCreateMutable();
CGPathAddRect(p, NULL, frameText);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), p, NULL);
CTFrameDraw(frame, contextP);
}
Here is a simple piece of code that will figure out the maximum font size to fit within the bounds of a frame:
UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.text = #"Some text";
float largestFontSize = 12;
while ([label.text sizeWithAttributes:#{NSFontAttributeName:[UIFont systemFontOfSize:largestFontSize]}].width > modifierFrame.size.width)
{
largestFontSize--;
}
label.font = [UIFont systemFontOfSize:largestFontSize];
The only way I can see this being possible is to have a system that runs the size calculation then adjusts the size and repeats until it finds the right size.
I.e. set up a bisecting algorithm that goes between certain sizes.
i.e. run it for size 10.
Too small.
Size 20.
Too small.
Size 30.
Too big.
Size 25.
Too small.
Size 27.
Just right, use size 27.
You could even start in hundreds.
Size 100.
Too big.
Size 50.
etc...
A little trick helps to make use of sizeWithAttributes: without the need of iterating for the right result:
NSSize sampleSize = [wordString sizeWithAttributes:
#{ NSFontAttributeName: [NSFont fontWithName:fontName size:fontSize] }];
CGFloat ratio = rect.size.width / sampleSize.width;
fontSize *= ratio;
Make sure the fontSize for the sample is big enough to get good results.
The currently accepted answer talks of an algorithm, but iOS provides calculations for an NSString object.
I would use sizeWithAttributes: of the NSString class.
sizeWithAttributes:
Returns the bounding box size the receiver occupies when drawn with the given attributes.
- (CGSize)sizeWithAttributes:(NSDictionary *)attributes
Source: Apple Docs - NSString UIKit Additions Reference
EDIT Misinterpreted the question, so this answer is off the mark.
Even more easy/faster (but of course approximate) way would be this:
class func calculateOptimalFontSize(textLength:CGFloat, boundingBox:CGRect) -> CGFloat
{
let area:CGFloat = boundingBox.width * boundingBox.height
return sqrt(area / textLength)
}
We are assuming each char is N x N pixels, so we just calculate how many times N x N goes inside bounding box.
You could use sizeWithFont :
[myString sizeWithFont:[UIFont fontWithName:#"HelveticaNeue-Light" size:24]
constrainedToSize:CGSizeMake(293, 10000)] // put the size of your frame
But it is deprecated in iOS 7, so I recommend if working with string in UILabel :
[string sizeWithAttributes:#{NSFontAttributeName:[UIFont systemFontOfSize:17.0f]}];
If you are working with a rect :
CGRect textRect = [text boundingRectWithSize:mySize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:FONT}
context:nil];
CGSize size = textRect.size;
You can set the UILabel's property adjustsFontSizeToFitWidth to YES as per Apple's documentation
Here is code which will do exactly that: calculate optimal font size within some bounds. This sample is in context of UITextView subclass, so it's using its bounds as a "given frame":
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.
Here is my solution in swift 4:
private func adjustedFontSizeOf(label: UILabel) -> CGFloat {
guard let textSize = label.text?.size(withAttributes: [.font: label.font]), textSize.width > label.bounds.width else {
return label.font.pointSize
}
let scale = label.bounds.width / textSize.width
let actualFontSize = scale * label.font.pointSize
return actualFontSize
}
I hope it helps someone.
I like the approach given by #holtwick, but found that it would sometimes overestimate what would fit. I created a tweak that seems to work well in my tests. Tip: Don't forget to test with really wide letters like "WWW" or even "௵௵௵"
func idealFontSize(for text: String, font: UIFont, width: CGFloat) -> CGFloat {
let baseFontSize = CGFloat(256)
let textSize = text.size(attributes: [NSFontAttributeName: font.withSize(baseFontSize)])
let ratio = width / textSize.width
let ballparkSize = baseFontSize * ratio
let stoppingSize = ballparkSize / CGFloat(2) // We don't want to loop forever, if we've already come down to 50% of the ballpark size give up
var idealSize = ballparkSize
while (idealSize > stoppingSize && text.size(attributes: [NSFontAttributeName: font.withSize(idealSize)]).width > width) {
// We subtract 0.5 because sometimes ballparkSize is an overestimate of a size that will fit
idealSize -= 0.5
}
return idealSize
}
Apple doesn't provides any method to find out a font size which fits the text in a given rect. Idea is to find out an optimal font size which perfectly fits the given size based on BinarySearch. Following extension tries different font sizes to converge to a perfect font size value.
import UIKit
extension UITextView {
#discardableResult func adjustFontToFit(_ rect: CGSize, minFontSize: CGFloat = 5, maxFontSize: CGFloat = 100, accuracy: CGFloat = 0.1) -> CGFloat {
// To avoid text overflow
let targetSize = CGSize(width: floor(rect.width), height: rect.height)
var minFontSize = minFontSize
var maxFontSize = maxFontSize
var fittingSize = targetSize
while maxFontSize - minFontSize > accuracy {
let midFontSize = (minFontSize + maxFontSize) / 2
font = font?.withSize(midFontSize)
fittingSize = sizeThatFits(targetSize)
if fittingSize.height <= rect.height {
minFontSize = midFontSize
} else {
maxFontSize = midFontSize
}
}
// It might be possible that while loop break with last assignment
// to `maxFontSize`, which can overflow the available height
// Using `minFontSize` will be failsafe
font = font?.withSize(minFontSize)
return minFontSize
}
}
This is the code to have dynamic font size changing by the frame width, using the logic from the other answers. The while loop might be dangerous, so please donot hesitate to submit improvements.
float fontSize = 17.0f; //initial font size
CGSize rect;
while (1) {
fontSize = fontSize+0.1;
rect = [watermarkText sizeWithAttributes:#{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]}];
if ((int)rect.width == (int)subtitle1Text.frame.size.width) {
break;
}
}
subtitle1Text.fontSize = fontSize;
Here's a method that seems to work well for iOS 9 using UITextView objects. You might have to tweet it a bit for other applications.
/*!
* Find the height of the smallest rectangle that will enclose a string using the given font.
*
* #param string The string to check.
* #param font The drawing font.
* #param width The width of the drawing area.
*
* #return The height of the rectngle enclosing the text.
*/
- (float) heightForText: (NSString *) string font: (UIFont *) font width: (float) width {
NSDictionary *fontAttributes = [NSDictionary dictionaryWithObject: font
forKey: NSFontAttributeName];
CGRect rect = [string boundingRectWithSize: CGSizeMake(width, INT_MAX)
options: NSStringDrawingUsesLineFragmentOrigin
attributes: fontAttributes
context: nil];
return rect.size.height;
}
/*!
* Find the largest font size that will allow a block of text to fit in a rectangle of the given size using the system
* font.
*
* The code is tested and optimized for UITextView objects.
*
* The font size is determined to ±0.5. Change delta in the code to get more or less precise results.
*
* #param string The string to check.
* #param size The size of the bounding rectangle.
*
* #return: The font size.
*/
- (float) maximumSystemFontSize: (NSString *) string size: (CGSize) size {
// Hack: For UITextView, the last line is clipped. Make sure it's not one we care about.
if ([string characterAtIndex: string.length - 1] != '\n') {
string = [string stringByAppendingString: #"\n"];
}
string = [string stringByAppendingString: #"M\n"];
float maxFontSize = 16.0;
float maxHeight = [self heightForText: string font: [UIFont systemFontOfSize: maxFontSize] width: size.width];
while (maxHeight < size.height) {
maxFontSize *= 2.0;
maxHeight = [self heightForText: string font: [UIFont systemFontOfSize: maxFontSize] width: size.width];
}
float minFontSize = maxFontSize/2.0;
float minHeight = [self heightForText: string font: [UIFont systemFontOfSize: minFontSize] width: size.width];
while (minHeight > size.height) {
maxFontSize = minFontSize;
minFontSize /= 2.0;
maxHeight = minHeight;
minHeight = [self heightForText: string font: [UIFont systemFontOfSize: minFontSize] width: size.width];
}
const float delta = 0.5;
while (maxFontSize - minFontSize > delta) {
float middleFontSize = (minFontSize + maxFontSize)/2.0;
float middleHeight = [self heightForText: string font: [UIFont systemFontOfSize: middleFontSize] width: size.width];
if (middleHeight < size.height) {
minFontSize = middleFontSize;
minHeight = middleHeight;
} else {
maxFontSize = middleFontSize;
maxHeight = middleHeight;
}
}
return minFontSize;
}

How to calculate actual font point size in iOS 7 (not the bounding rectangle)?

Edit: The linked "duplicate" question only deals with calculating text rectangle. I need to calculate actual font size after label scaled it, NOT the string size.
This method is now deprecated:
size = [self sizeWithFont:font // 20
minFontSize:minFontSize // 14
actualFontSize:&actualFontSize // 16
forWidth:maxWidth
lineBreakMode:self.lineBreakMode];
How can I calculate font size of a UILabel now in iOS 7 when it shrunk the size of the text to fit in?
I have the same problem, I need to know the actual size to make that the others UILabels in my UIView match.
I know that it's not a perfect solution, but perhaps it's useful for you.
My solution is: instead of use adjustsFontSizeToFitWidth I calculate "manually" the size.
CGSize initialSize = [_label.text sizeWithAttributes:#{NSFontAttributeName:_label.font}];
while ( initialSize.width > _label.frame.size.width ) {
[_label setFont:[_label.font fontWithSize:_label.font.pointSize - 1]];
initialSize = [_label.text sizeWithAttributes:#{NSFontAttributeName:_label.font}];
}
CGFloat actualSize = _label.font.pointSize;
Distilled from Julius Bahr's answer on this page, this method works perfectly for getting the actual font size after it has been automatically adjusted:
- (CGFloat)getActualFontSizeForLabel:(UILabel *)label
{
NSStringDrawingContext *labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = label.minimumScaleFactor;
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:label.text attributes:#{ NSFontAttributeName: label.font }];
[attributedString boundingRectWithSize:label.frame.size
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
context:labelContext];
CGFloat actualFontSize = label.font.pointSize * labelContext.actualScaleFactor;
return actualFontSize;
}
I am using this in my application to get the font sizes for three different labels for which I need to keep the sizes in synch while still allowing them to auto-shrink for localized translations that can be quite a bit longer than their original English counterparts.
I call that method once for each label, and then if they are not all the same value, I set the label's font sizes to the minimum of the three.
The use of minFontSize was deprecated on UILabel in iOS 6, and on the NSString drawing additions in iOS 7. If you want to use it and find the actual font size used, you need to use the deprecated method you mentioned in your question.
The replacement for minFontSize is minimumScaleFactor. If you want to find the actual scale factor used, you need to create an NSStringDrawingContext and pass it in the boundingRectWithSize:options:attributes:context: message, like this:
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
context.minimumScaleFactor = 0.7;
[label.text boundingRectWithSize:CGSizeMake(maxWidth, HUGE_VAL)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName: font
} context:context];
CGFloat actualFontSize = font.pointSize * context.actualScaleFactor;
Expanding on Ferran's answer
To expand to fill width or height, whichever it hits first
Swift version
func getFontSizeToFitFrameOfLabel(label: UILabel) -> CGFloat
{
var initialSize : CGSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])
if initialSize.width > label.frame.size.width ||
initialSize.height > label.frame.size.height
{
while initialSize.width > label.frame.size.width ||
initialSize.height > label.frame.size.height
{
label.font = label.font.fontWithSize(label.font.pointSize - 1)
initialSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])
}
} else {
while initialSize.width < label.frame.size.width &&
initialSize.height < label.frame.size.height
{
label.font = label.font.fontWithSize(label.font.pointSize + 1)
initialSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])
}
// went 1 point too large so compensate here
label.font = label.font.fontWithSize(label.font.pointSize - 1)
}
return label.font.pointSize;
}
Then do something like this to use it (say your label is named title1Label)
title1Label.frame = CGRect(x: 0.0, y: 0.0, width: view.frame.size.width, height: view.frame.size.height)
// sets font to some nonzero size to begin with, it will change up or down to fit the label's frame
title1Label.font = UIFont(name: "Super Mario 256", size: 45.0)
title1Label.font = title1Label.font.fontWithSize(getFontSizeToFitFrameOfLabel(title1Label))
// resize height to be a little larger than the font height
title1Label.frame.size.height = title1Label.font.pointSize*1.3
Objective C version:
- (CGFloat) maxFontSize:(UILabel *)label{
CGSize initialSize = [label.text sizeWithAttributes:#{NSFontAttributeName:label.font}];
if (initialSize.width > label.frame.size.width ||
initialSize.height > label.frame.size.height)
{
while (initialSize.width > label.frame.size.width ||
initialSize.height > label.frame.size.height)
{
[label setFont:[label.font fontWithSize:label.font.pointSize - 1]];
initialSize = [label.text sizeWithAttributes:#{NSFontAttributeName:label.font}];
}
} else {
while (initialSize.width < label.frame.size.width &&
initialSize.height < label.frame.size.height)
{
[label setFont:[label.font fontWithSize:label.font.pointSize + 1]];
initialSize = [label.text sizeWithAttributes:#{NSFontAttributeName:label.font}];
}
// went 1 point too large so compensate here
[label setFont:[label.font fontWithSize:label.font.pointSize - 1]];
}
return label.font.pointSize;
}
My specific quest has been to size the font on 2 labels equally with adjustsFontSizeToFitWidth enabled.
The solution works on iOS 6 and 7.
+ (void)sizeLabelFontToMinSizeFor:(UILabel *)label1 and:(UILabel *)label2 {
NSStringDrawingContext *labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = label1.minimumScaleFactor;
NSAttributedString *attributedString1 = [[NSAttributedString alloc] initWithString:label1.text attributes:#{NSFontAttributeName : label1.font}];
// the NSStringDrawingUsesLineFragmentOrigin and NSStringDrawingUsesFontLeading options are magic
[attributedString1 boundingRectWithSize:label1.frame.size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:labelContext];
CGFloat actualFontSize1 = label1.font.pointSize * labelContext.actualScaleFactor;
labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = label2.minimumScaleFactor;
NSAttributedString *attributedString2 = [[NSAttributedString alloc] initWithString:label2.text attributes:#{NSFontAttributeName : label2.font}];
[attributedString2 boundingRectWithSize:label2.frame.size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:labelContext];
CGFloat actualFontSize2 = label2.font.pointSize * labelContext.actualScaleFactor;
CGFloat minSize = MIN(actualFontSize1, actualFontSize2);
label1.font = [UIFont fontWithName:RCDefaultFontName size:minSize];
label2.font = [UIFont fontWithName:RCDefaultFontName size:minSize];
}
Next code doesn't support minFontSize and lineBreakMode so if you need them you should improve it by yourself:
CGSize NSString_sizeWithFont(NSString * str, UIFont *font) {
CGSize result;
if (NO == [str respondsToSelector: #selector(sizeWithAttributes:)]) {
// legacy way
result = [str sizeWithFont: font];
} else {
// modern way
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName, nil];
result = [str sizeWithAttributes: dict];
}
return result;
}
UIFont * NSString_calcActualFont(NSString * str, UIFont * initialFont,
CGSize sizeLimit, CGSize * actualSize)
{
const CGSize curSize = NSString_sizeWithFont(str, initialFont);
CGFloat actualFontSize = initialFont.pointSize;
actualFontSize *= MIN(sizeLimit.width / curSize.width, sizeLimit.height / curSize.height);
UIFont * actualFont = [initialFont fontWithSize: floorf(actualFontSize)];
*actualSize = NSString_sizeWithFont(str, actualFont);
return actualFont;
}
Simple solution for one-line UILabel:
//myLabel - initial label
UILabel *fullSizeLabel = [UILabel new];
fullSizeLabel.font = myLabel.font;
fullSizeLabel.text = myLabel.text;
[fullSizeLabel sizeToFit];
CGFloat actualFontSize = myLabel.font.pointSize * (myLabel.bounds.size.width / fullSizeLabel.bounds.size.width);
//correct, if new font size bigger than initial
actualFontSize = actualFontSize < myLabel.font.pointSize ? actualFontSize : myLabel.font.pointSize;
Erik van der Neut's code worked for me, so I translated it in Swift and wrapped it in a UILabel extension:
extension UILabel {
public func actualFontSize()-> CGFloat {
let context = NSStringDrawingContext()
context.minimumScaleFactor = self.minimumScaleFactor
let attributedString = NSAttributedString(string: self.text ?? "", attributes: [NSFontAttributeName: self.font])
attributedString.boundingRectWithSize(self.frame.size, options: [.UsesLineFragmentOrigin], context: context)
return (self.font.pointSize * context.actualScaleFactor)
}
}

Resources