91

How can I find the actual number of lines of a UILabel after I have initialized it with a text and a font? I have set its numberOfLines property to 0, so it will expand to however many lines are necessary. But then, how can I find out how many lines it finally got after I set its text?

I found similar questions, but none seems to provide a concise answer and it seems to me that it must be really easy to get it without any overhead on juggling around with the boundingRectWithSize or sizeWithFont,...

nburk
  • 22,409
  • 18
  • 87
  • 132

17 Answers17

93

None of these worked for me. Below one did,

Swift 4.2:

extension UILabel {
    func calculateMaxLines() -> Int {
        let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
        let charSize = font.lineHeight
        let text = (self.text ?? "") as NSString
        let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
        let linesRoundedUp = Int(ceil(textSize.height/charSize)) 
        return linesRoundedUp
    }    
}

Swift 4/4.1:

extension UILabel {

    func calculateMaxLines() -> Int {
        let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
        let charSize = font.lineHeight
        let text = (self.text ?? "") as NSString
        let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
        let linesRoundedUp = Int(ceil(textSize.height/charSize)) 
        return linesRoundedUp
    }

}

Swift 3:

extension UILabel {

    func calculateMaxLines() -> Int {
        let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
        let charSize = font.lineHeight
        let text = (self.text ?? "") as NSString
        let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
        let linesRoundedUp = Int(ceil(textSize.height/charSize)) 
        return linesRoundedUp
    }

}
Kurt J
  • 2,558
  • 24
  • 17
  • Lines should be rounded up. let lines = textSize.height / charSize let linesRounded = Int(ceil(lines)) return linesRounded – Tommy Sadiq Hinrichsen Oct 17 '18 at 10:02
  • @TommySadiqHinrichsen I don't think that would ever factor in, textSize.height/charSize should be equivalent to an exact integer anyway. Do you have a scenario where it wouldn't? – Kurt J Oct 17 '18 at 18:39
  • When using spacing between lines. I can see your point but i think is more robust since we all know sometimes strange things happen with floating point numbers. – Tommy Sadiq Hinrichsen Oct 18 '18 at 07:32
  • Yeah, but I think that would apply only if you specified a paragraph style when calculating the boudingRect. I guess I can change it because I don't see any harm it could do. – Kurt J Oct 18 '18 at 13:42
  • it calculates invisible lines too... next answer is more correct if you need only visible lines https://stackoverflow.com/a/44086468/7767664 – user924 Apr 17 '19 at 21:21
  • This is an OK answer but if you are using word wrapping it will occasionally give you wrong answers. The answer by @sam, below gives the best result. – Robert Schmid Oct 02 '19 at 23:16
  • 1
    `frame.size.width` sometimes returns 0 and it gives wrong maxSize and based on that textSize. In my case, setting `UIScreen.main.bounds.width` as width to maxSize solved the problem – lmboom Dec 23 '20 at 11:06
79

Swift 5 (IOS 12.2)

Get max number of lines required for a label to render the text without truncation.

extension UILabel {
    var maxNumberOfLines: Int {
        let maxSize = CGSize(width: frame.size.width, height: CGFloat(MAXFLOAT))
        let text = (self.text ?? "") as NSString
        let textHeight = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil).height
        let lineHeight = font.lineHeight
        return Int(ceil(textHeight / lineHeight))
    }
}

Get max number of lines can be displayed in a label with constrained bounds. Use this property after assigning text to label.

extension UILabel {
    var numberOfVisibleLines: Int {
        let maxSize = CGSize(width: frame.size.width, height: CGFloat(MAXFLOAT))
        let textHeight = sizeThatFits(maxSize).height
        let lineHeight = font.lineHeight
        return Int(ceil(textHeight / lineHeight))
    }
}

Usage

print(yourLabel.maxNumberOfLines)
print(yourLabel.numberOfVisibleLines)
ramchandra n
  • 1,957
  • 1
  • 15
  • 15
  • 7
    It gave one more line for me, number of visible lines was 5 but your method returned 6. – Markicevic Aug 01 '17 at 09:23
  • 2
    `let zone = CGSize(width: intrinsicContentSize.width, height: CGFloat(MAXFLOAT)) let fittingHeight = Float(self.sizeThatFits(zone).height) return lroundf(fittingHeight / Float(font.lineHeight))` calculates the correct lines for me – Azules Apr 18 '19 at 04:14
  • This is an OK answer but if you are using word wrapping it will occasionally give you wrong answers. The answer by @sam, below gives the best result. – Robert Schmid Oct 02 '19 at 23:16
  • 1
    label.layoutIfNeeded() is necessary for AutoLayout – Jeff Apr 23 '21 at 21:33
63

Firstly set text in UILabel

First Option :

Firstly calculate height for text according to font :

NSInteger lineCount = 0;
CGSize labelSize = (CGSize){yourLabel.frame.size.width, MAXFLOAT};
CGRect requiredSize = [self boundingRectWithSize:labelSize  options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: yourLabel.font} context:nil];

Now calculate number of lines :

int charSize = lroundf(yourLabel.font.lineHeight);
int rHeight = lroundf(requiredSize.height);
lineCount = rHeight/charSize;
NSLog(@"No of lines: %i",lineCount);

Second Option :

 NSInteger lineCount = 0;
 CGSize textSize = CGSizeMake(yourLabel.frame.size.width, MAXFLOAT);
 int rHeight = lroundf([yourLabel sizeThatFits:textSize].height);
 int charSize = lroundf(yourLabel.font.lineHeight);
 lineCount = rHeight/charSize;
 NSLog(@"No of lines: %i",lineCount);
Community
  • 1
  • 1
Paresh Navadiya
  • 38,095
  • 11
  • 81
  • 132
  • 5
    int charSize = lroundf(yourLabel.font.lineHeight); – Brooks Hanes Nov 27 '15 at 20:42
  • 1
    I think you should round off after the division rHeight/charSize, rounding off rHeight and charSize may result in loosing one line, which was in my case. – ambientlight Mar 25 '16 at 10:07
  • 2
    Like @BrooksHanes mentioned-- leading isn't the same as lineHeight is `The leading value represents additional space between lines of text and is measured in points.` (https://developer.apple.com/reference/uikit/uifont/1619026-leading). – Chris Prince Jan 05 '17 at 23:47
  • Also-- `boundingRectWithSize` is a method of NSString. – Chris Prince Jan 05 '17 at 23:51
  • `lineHeight` indeed works and `leading` doesn't (at least for me). Updated the answer – yonix Jun 13 '17 at 12:16
  • Tried the second approach and it calculates the number of lines correctly. Verified with the different-2 set of strings always the correct number of lines returned. – Kamar Shad Aug 03 '18 at 05:47
  • This is an OK answer but if you are using word wrapping it will occasionally give you wrong answers. The answer by @sam, below gives the best result. – Robert Schmid Oct 02 '19 at 23:15
22

Here is a swift version of @Paresh solution:

func lines(label: UILabel) -> Int {
    let textSize = CGSize(width: label.frame.size.width, height: CGFloat(Float.infinity))
    let rHeight = lroundf(Float(label.sizeThatFits(textSize).height))
    let charSize = lroundf(Float(label.font.lineHeight))
    let lineCount = rHeight/charSize
    return lineCount
}

EDIT: I don't know why, but the code is returning 2 more lines than the actual number of lines, for my solution, I just subtracted them before returning lineCount.

danywarner
  • 928
  • 2
  • 15
  • 28
14

Swift 5.2

The main point to make it work for me was to call label.layoutIfNeeded() because I was using autoLayout, otherwise it doesnt work.

func actualNumberOfLines(label: UILabel) -> Int {
        // You have to call layoutIfNeeded() if you are using autoLayout
        label.layoutIfNeeded()

        let myText = label.text! as NSString

        let rect = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
        let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: label.font as Any], context: nil)

        return Int(ceil(CGFloat(labelSize.height) / label.font.lineHeight))
    }

Credits to: https://gist.github.com/fuxingloh/ccf26bb68f4b8e6cfd02, which provided the solution in an older swift version, and for mentioning the importance of layoutIfNeeded().

Fernando Cardenas
  • 1,203
  • 15
  • 19
  • var actualNumberOfLines: Int { layoutIfNeeded() let myText = text! as NSString let rect = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude) let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font as Any], context: nil) return Int(ceil(CGFloat(labelSize.height) / font.lineHeight)) } – Bunthoeun Jul 30 '20 at 09:26
12

The other answers here don't respect the numberOfLines property of UILabel when it is set to something other than 0.

Here's another option you can add to your category or subclass:

- (NSUInteger)lineCount
{
    CGSize size = [self sizeThatFits:CGSizeMake(self.frame.size.width, CGFLOAT_MAX)];
    return MAX((int)(size.height / self.font.lineHeight), 0);
}

Some notes:

  • I'm using this on a UILabel with attributed text, without ever actually setting the font property, and it's working fine. Obviously you would run into issues if you were using multiple fonts in your attributedText.
  • If you are subclassing UILabel to have custom edge insets (for example by overriding drawTextInRect:, which is a neat trick I found here), then you must remember to take those insets into account when calculating the size above. For example: CGSizeMake(self.frame.size.width - (self.insets.left + self.insets.right), CGFLOAT_MAX)
Community
  • 1
  • 1
pejalo
  • 923
  • 11
  • 24
10

Here is the Swift3 Code here you can define Int value and get the height of text size by using (MAXFLOAT) and using that height you can get the total height of UILabel and by deviding that total height by character size you can get the actual line count of UILabel.

var lineCount: Int = 0
var textSize = CGSize(width: CGFloat(yourLabel.frame.size.width), height: CGFloat(MAXFLOAT))
var rHeight: Int = lroundf(yourLabel.sizeThatFits(textSize).height)
var charSize: Int = lroundf(yourLabel.font.leading)
lineCount = rHeight / charSize
print("No of lines: \(lineCount)")
Azharhussain Shaikh
  • 1,654
  • 14
  • 17
  • Whilst this code snippet is welcome, and may provide some help, it would be [greatly improved if it included an explanation](//meta.stackexchange.com/q/114762) of *how* and *why* this solves the problem. Remember that you are answering the question for readers in the future, not just the person asking now! Please [edit] your answer to add explanation, and give an indication of what limitations and assumptions apply. – Toby Speight Apr 05 '17 at 12:05
7

It seems that the official developer website mentions one solution Counting Lines of Text in Objc. However, it assumes you have a reference to a text view configured with a layout manager, text storage, and text container. Unfortunately, UILabel doesn't expose those to us, so we need create them with the same configuration as the UILabel.

I translated the Objc code to swift as following. It seems work well for me.

extension UILabel {
    var actualNumberOfLines: Int {
        let textStorage = NSTextStorage(attributedString: self.attributedText!)
        let layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
        let textContainer = NSTextContainer(size: self.bounds.size)
        textContainer.lineFragmentPadding = 0
        textContainer.lineBreakMode = self.lineBreakMode
        layoutManager.addTextContainer(textContainer)

        let numberOfGlyphs = layoutManager.numberOfGlyphs
        var numberOfLines = 0, index = 0, lineRange = NSMakeRange(0, 1)

        while index < numberOfGlyphs {
            layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
            index = NSMaxRange(lineRange)
            numberOfLines += 1
        }
        return numberOfLines
    }
}
sam
  • 1,767
  • 12
  • 15
  • 1
    This is the best answer. The other answers fail to properly account for word wrapping which can result in a number of lines which is too short because they are effectively using character wrapping. – Robert Schmid Oct 02 '19 at 23:14
  • One caveat, make sure you use an NSAttributedString. If you try to make a NSTextStorage from just text, you will get random results. – Robert Schmid Oct 02 '19 at 23:19
  • This doesn't work well when providing a label that has text in RTL. it makes the text left aligned. – Yarneo Oct 26 '19 at 22:21
  • 3
    always return number of line 1 , – Dhiru Jan 08 '20 at 09:51
6

You can find the total number of line available in your custom label Please check this code...

NSInteger numberOfLines = [self lineCountForText:@"YOUR TEXT"];

- (int)lineCountForText:(NSString *) text
{
    UIFont *font = [UIFont systemFontOfSize: 15.0];
    int width=Your_LabelWidht;

    CGRect rect = [text boundingRectWithSize:CGSizeMake(width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin  attributes:@{NSFontAttributeName : font} context:nil];
    return ceil(rect.size.height / font.lineHeight);
}
Kaushik Movaliya
  • 799
  • 11
  • 27
5

Following up on @Prince's answer, I now implemented a category on UILabel as follows (note that I corrected some minor syntax mistakes in his answer that wouldn't let the code compile):

UILabel+Util.h

#import <UIKit/UIKit.h>

@interface UILabel (Util)
- (NSInteger)lineCount;
@end

UILabel+Util.,

#import "UILabel+Util.h"

@implementation UILabel (Util)

- (NSInteger)lineCount
{
    // Calculate height text according to font
    NSInteger lineCount = 0;
    CGSize labelSize = (CGSize){self.frame.size.width, FLT_MAX};
    CGRect requiredSize = [self.text boundingRectWithSize:labelSize  options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: self.font} context:nil];

    // Calculate number of lines
    int charSize = self.font.leading;
    int rHeight = requiredSize.size.height;
    lineCount = rHeight/charSize;

    return lineCount;
}

@end
nburk
  • 22,409
  • 18
  • 87
  • 132
  • 5
    Apple documentation of `leading` reads "Use the `lineHeight` property instead." In fact, on my `UILabel`, the font's `leading` property equals 0. – Gerard Nov 09 '15 at 16:12
2

Xamarin iOS

label.Text = text;

var lineCount = 0;
var textSize = new CGSize(label.Frame.Size.Width, float.MaxValue);
var height = label.SizeThatFits(textSize).Height;
var fontHeight = label.Font.LineHeight;

lineCount = Convert.ToInt32(height / fontHeight);
adnan
  • 141
  • 2
  • 12
2
let l = UILabel()
l.numberOfLines = 0
l.layer.frame.size.width = self.view.frame.width - 40 /*padding(20 + 20)*/

l.font = UIFont(name: "BwModelica-Bold", size: 16.0)

l.text = "Random Any length Text!!"

let noOfLines = ceil(l.intrinsicContentSize.width / l.frame.size.width)

let lbl_height = noOfLines * l.intrinsicContentSize.height

This will be your Exact dynamic height of Label and Number of lines. Happy coding!!!

oskarko
  • 3,382
  • 1
  • 26
  • 26
onkar dhanlobhe
  • 231
  • 2
  • 13
2

Note that @kurt-j's answer will not always work. In some cases, you will have to manually provide the width of label. Since these cases exist, it is a good idea to have an optional width parameter, even if you don't end up using it.

Swift 4.2:

extension UILabel {
    func calculateMaxLines(actualWidth: CGFloat?) -> Int {
        var width = frame.size.width
        if let actualWidth = actualWidth {
            width = actualWidth
        }
        let maxSize = CGSize(width: width, height: CGFloat(Float.infinity))
        let charSize = font.lineHeight
        let text = (self.text ?? "") as NSString
        let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
        let linesRoundedUp = Int(ceil(textSize.height/charSize)) 
        return linesRoundedUp
    }    
}
pyromancer2
  • 173
  • 1
  • 1
  • 6
1

Xamarin.iOS

Thanks to the answers everyone provided above.

This gets number of visible lines.

public static int VisibleLineCount(this UILabel label)
{
    var textSize = new CGSize(label.Frame.Size.Width, nfloat.MaxValue);
    nfloat rHeight = label.SizeThatFits(textSize).Height;
    nfloat charSize = label.Font.LineHeight;
    return Convert.ToInt32(rHeight / charSize);
}

This gets actual number of lines the text will occupy on screen.

public static int LineCount(this UILabel label)
{
    var maxSize = new CGSize(label.Frame.Size.Width, nfloat.MaxValue);
    var charSize = label.Font.LineHeight;
    var text = (label.Text ?? "").ToNSString();
    var textSize = text.GetBoundingRect(maxSize, NSStringDrawingOptions.UsesLineFragmentOrigin, new UIStringAttributes() { Font = label.Font }, null);
    return Convert.ToInt32(textSize.Height / charSize);
}

A helper method I find useful for my use case.

public static bool IsTextTruncated(this UILabel label)
{
    if (label.Lines == 0)
    {
        return false;
    }
    return (label.LineCount() > label.Lines);
 }

To get a more accurate line count:

  • Use font.lineHeight instead of font.pointSize
  • round() the line count after division
Baron Ch'ng
  • 181
  • 8
1

Swift 5.4 Refactor solution of Fernando Cardenas to UILabel extension

private extension UILabel {
    var actualNumberOfLines: Int {
        guard let text = self.text else {
            return 0
        }
        layoutIfNeeded()
        let rect = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
        let labelSize = text.boundingRect(
            with: rect,
            options: .usesLineFragmentOrigin,
            attributes: [NSAttributedString.Key.font: font as Any],
            context: nil)
        return Int(ceil(CGFloat(labelSize.height) / font.lineHeight))
    }
}
Tony Macaren
  • 437
  • 5
  • 11
0

⚠️ Nor lineHeight, nor leading is sufficient on its own.

The font.lineHeight is the height of the glyphs, it spans from descender (the bottom of the lowest glyph) to ascender (the top of the highest glyph). Also, note that lineHeight can be override for an attributed string. The font.leading is the additional (may be negative, though) space between (!) the lines (see docs for yourself). If you use the system fonts, you get different leading values for almost every point size.

So e.g. the height of a label with 5 lines is consist of 5 lineHeight and 4 leading (yet the leading value is often small enough to make the above solutions work up until a point where you start to work with a multitude of lines.

So the correct (pseudo) formula should be:

(frame.size.height + font.leading) / (font.lineHeight + font.leading)

Also also, if the attributed string has an attachment that is too big (higher than the ascender of the font), then it also alters the line height for that row.

Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
0

This is the only accurate way I could find:

  -(int)numberOfLines:(UILabel *)label{

NSArray * splitWords = [label.text componentsSeparatedByString:@" "];

int lineCount = 1;
float currentLineWidth = 0.0f;

for (int n = 0; n < (int)splitWords.count; n++){
    
    NSString * word = splitWords[n];
    NSString * spaceWord = [NSString stringWithFormat:@"%@ ", word];
    float wordWidth = [spaceWord sizeWithAttributes:@{NSFontAttributeName:label.font}].width;
    float potentialWidth = currentLineWidth + wordWidth;
    
    if (potentialWidth <= label.frame.size.width){
        
        currentLineWidth = potentialWidth;
        
    } else {
        
        wordWidth = [word sizeWithAttributes:@{NSFontAttributeName:label.font}].width; //try without the space
        potentialWidth = currentLineWidth + wordWidth;
        
        if (potentialWidth <= label.frame.size.width){
            
            currentLineWidth = potentialWidth;
            
        } else {
            
            lineCount++;
            currentLineWidth = wordWidth;
        }
    }
}

return lineCount;
}

Iterate over the words and see when they break the lines, increment a counter when they do. You can adapt to collect the actual words per line as well, if that's useful to you.

Johnny Rockex
  • 4,136
  • 3
  • 35
  • 55