Calculating UILabel Text Size
Asked Answered
O

12

56

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.

Overweary answered 2/10, 2013 at 1:49 Comment(0)
O
2

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.

Overweary answered 4/10, 2013 at 3:4 Comment(0)
M
69

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)
Motivation answered 2/10, 2013 at 2:5 Comment(3)
the "problem" you solved in your answer is different than the question you asked, at least as I understood it. You are asking how you can determine the largest font size you can use so that text fits within a given label bounds. Configure the UILabel with adjustFontSizeToFitWidth and minimumScaleFactor and iOS will do the work for you.Motivation
your approach to this is wrong. when content changes (e.g. text in a label) you relayout your views based on the content. rather than trying to determine if the new text value fits in the existing labels bounds just calc the bounds needed based on the text and size the label appropriately. I've been trying to help you but I have nothing more to add.Motivation
I am not adding any new text. The reason why I am doing this is because I want to be able to take the screenshot of UIView and I want every label's text to be visible, therefore once I need to take the screenshot I simply rescale labels(which is not added to subview by the way) and save it as a PNG file. Why would my approach be wrong?Overweary
S
51

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.

Sunshinesunspot answered 14/12, 2014 at 11:37 Comment(1)
please note that this doesn't work with multiple lines UILabelMatilda
R
30

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.)

Rubella answered 14/10, 2015 at 21:52 Comment(0)
L
18

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)
Lepage answered 25/9, 2015 at 17:18 Comment(1)
Didn't seem to work with UILabel. So I used UITextView and it did the trick.Courtenay
V
3

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!

Vinic answered 12/12, 2014 at 15:42 Comment(2)
I'd suggest never working with int for screen sizes or location, just stick to CGFloat.Plane
In swift you can still use CGFloat (e.g. var x:CGFloat = 14)Terra
O
2

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.

Overweary answered 4/10, 2013 at 3:4 Comment(0)
S
2

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
Siloum answered 20/11, 2018 at 15:40 Comment(0)
F
1

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.

Freyah answered 4/11, 2016 at 5:43 Comment(1)
u need to provide the font used in your appFreyah
K
0

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
Kalpa answered 10/11, 2016 at 5:58 Comment(0)
D
0

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
}
Doerrer answered 18/11, 2017 at 8:55 Comment(0)
A
0

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;
        }
    }
Aricaarick answered 21/12, 2021 at 15:51 Comment(0)
H
0

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
    }
}
Haggerty answered 7/5, 2022 at 17:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.