如何查找 UILabel 的实际行数?

在我用 textfont初始化一个 UILabel之后,我怎样才能找到它的 真的行数?我已经将它的 numberOfLines属性设置为 0,因此它将扩展到多少行是必要的。但是,我怎样才能知道在我设置了它的 text之后它最终得到了多少行呢?

我发现类似的问题,但似乎没有提供一个简明的答案,它似乎是真的很容易得到它没有任何开销与 boundingRectWithSizesizeWithFont左右杂耍,..。

80696 次浏览

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

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

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)

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.

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

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)

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

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

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
}


}

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

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

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

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

⚠️ Nor ABC0, 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.

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