How to find actual number of lines of UILabel?

IosUilabel

Ios Problem Overview


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

Ios Solutions


Solution 1 - Ios

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

Solution 2 - Ios

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)

Solution 3 - Ios

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

Solution 4 - Ios

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.

Solution 5 - Ios

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

Solution 6 - Ios

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)

Solution 7 - Ios

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

Solution 8 - Ios

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

Solution 9 - Ios

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

Solution 10 - Ios

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

Solution 11 - Ios

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

Solution 12 - Ios

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

Solution 13 - Ios

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

Solution 14 - Ios

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

Solution 15 - Ios

⚠️ 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.

Solution 16 - Ios

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnburkView Question on Stackoverflow
Solution 1 - IosKurt JView Answer on Stackoverflow
Solution 2 - Iosramchandra nView Answer on Stackoverflow
Solution 3 - IosParesh NavadiyaView Answer on Stackoverflow
Solution 4 - IosdanywarnerView Answer on Stackoverflow
Solution 5 - IosFernando CardenasView Answer on Stackoverflow
Solution 6 - IospejaloView Answer on Stackoverflow
Solution 7 - IosAzharhussain ShaikhView Answer on Stackoverflow
Solution 8 - IosKaushik MovaliyaView Answer on Stackoverflow
Solution 9 - IossamView Answer on Stackoverflow
Solution 10 - IosnburkView Answer on Stackoverflow
Solution 11 - IosadnanView Answer on Stackoverflow
Solution 12 - Iosonkar dhanlobheView Answer on Stackoverflow
Solution 13 - Iospyromancer2View Answer on Stackoverflow
Solution 14 - IosBaron Ch'ngView Answer on Stackoverflow
Solution 15 - IosGeri BorbásView Answer on Stackoverflow
Solution 16 - IosTony MacarenView Answer on Stackoverflow