Reputation: 22751
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
,...
Upvotes: 92
Views: 92291
Reputation: 457
Swift 5.4 Refactor solution of Fernando Cardenas to UILabel extension
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))
}
}
Upvotes: 1
Reputation: 4196
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.
Upvotes: 0
Reputation: 231
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!!!
Upvotes: 2
Reputation: 16628
⚠️ 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.
Upvotes: 0
Reputation: 1232
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().
Upvotes: 15
Reputation: 1807
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
}
}
Upvotes: 7
Reputation: 2027
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)
Upvotes: 82
Reputation: 173
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
}
}
Upvotes: 2
Reputation: 2603
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
}
}
Upvotes: 97
Reputation: 181
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:
font.lineHeight
instead of font.pointSize
Upvotes: 1
Reputation: 141
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);
Upvotes: 2
Reputation: 1664
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)")
Upvotes: 10
Reputation: 958
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.
Upvotes: 22
Reputation: 857
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);
}
Upvotes: 6
Reputation: 38259
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);
Upvotes: 63
Reputation: 981
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:
font
property, and it's working fine. Obviously you would run into issues if you were using multiple fonts in your attributedText
.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)
Upvotes: 12
Reputation: 22751
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):
#import <UIKit/UIKit.h>
@interface UILabel (Util)
- (NSInteger)lineCount;
@end
#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
Upvotes: 5