Sarp Kaya
Sarp Kaya

Reputation: 3784

Calculating UILabel Text Size

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.

Upvotes: 57

Views: 100773

Answers (12)

clearlight
clearlight

Reputation: 12615

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
    }
}

Upvotes: 0

Anton Tropashko
Anton Tropashko

Reputation: 5806

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;
        }
    }

Upvotes: 0

chrisben
chrisben

Reputation: 955

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

Upvotes: 2

Abhishek Jain
Abhishek Jain

Reputation: 4739

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
}

Upvotes: 0

H. Serdar &#199;ınar
H. Serdar &#199;ınar

Reputation: 1046

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.

Upvotes: 51

Collin
Collin

Reputation: 6760

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)

Upvotes: 18

XJones
XJones

Reputation: 21967

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)

Upvotes: 69

Jayesh Miruliya
Jayesh Miruliya

Reputation: 3317

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

Upvotes: 0

Ramakrishna
Ramakrishna

Reputation: 752

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.

Upvotes: 1

poff
poff

Reputation: 1640

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

Upvotes: 30

Martin
Martin

Reputation: 12215

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!

Upvotes: 3

Sarp Kaya
Sarp Kaya

Reputation: 3784

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.

Upvotes: 2

Related Questions