How to check if UILabel is truncated? - ios
I have a UILabel that can be varying lengths depending on whether or not my app is running in portrait or landscape mode on an iPhone or iPad. When the text is too long to show on one line and it truncates I want the user to be able to press it and get a popup of the full text.
How can I check to see if the UILabel is truncating the text? Is it even possible? Right now I'm just checking for different lengths based on what mode I'm in but it does not work super well.
You can calculate the width of the string and see if the width is greater than label.bounds.size.width
NSString UIKit Additions has several methods for computing the size of the string with a specific font. However, if you have a minimumFontSize for your label that allows the system to shrink the text down to that size. You may want to use sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode: in that case.
CGSize size = [label.text sizeWithAttributes:#{NSFontAttributeName:label.font}];
if (size.width > label.bounds.size.width) {
...
}
Swift (as extension) - works for multi line uilabel:
swift4: (attributes param of boundingRect changed slightly)
extension UILabel {
var isTruncated: Bool {
guard let labelText = text else {
return false
}
let labelTextSize = (labelText as NSString).boundingRect(
with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: font],
context: nil).size
return labelTextSize.height > bounds.size.height
}
}
swift3:
extension UILabel {
var isTruncated: Bool {
guard let labelText = text else {
return false
}
let labelTextSize = (labelText as NSString).boundingRect(
with: CGSize(width: frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
return labelTextSize.height > bounds.size.height
}
}
swift2:
extension UILabel {
func isTruncated() -> Bool {
if let string = self.text {
let size: CGSize = (string as NSString).boundingRectWithSize(
CGSize(width: self.frame.size.width, height: CGFloat(FLT_MAX)),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: self.font],
context: nil).size
if (size.height > self.bounds.size.height) {
return true
}
}
return false
}
}
EDIT: I just saw my answer was upvoted, but the code snippet I gave is deprecated.
Now the best way to do this is (ARC) :
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.lineBreakMode = mylabel.lineBreakMode;
NSDictionary *attributes = #{NSFontAttributeName : mylabel.font,
NSParagraphStyleAttributeName : paragraph};
CGSize constrainedSize = CGSizeMake(mylabel.bounds.size.width, NSIntegerMax);
CGRect rect = [mylabel.text boundingRectWithSize:constrainedSize
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:attributes context:nil];
if (rect.size.height > mylabel.bounds.size.height) {
NSLog(#"TOO MUCH");
}
Note the calculated size is not integer value. So if you do things like int height = rect.size.height, you will lose some floating point precision and may have wrong results.
Old answer (deprecated) :
If your label is multiline, you can use this code :
CGSize perfectSize = [mylabel.text sizeWithFont:mylabel.font constrainedToSize:CGSizeMake(mylabel.bounds.size.width, NSIntegerMax) lineBreakMode:mylabel.lineBreakMode];
if (perfectSize.height > mylabel.bounds.size.height) {
NSLog(#"TOO MUCH");
}
Swift 3
You can count the number of lines after assigning the string and compare to the max number of lines of the label.
import Foundation
import UIKit
extension UILabel {
func countLabelLines() -> Int {
// Call self.layoutIfNeeded() if your view is uses auto layout
let myText = self.text! as NSString
let attributes = [NSFontAttributeName : self.font]
let labelSize = myText.boundingRect(with: CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attributes, context: nil)
return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight))
}
func isTruncated() -> Bool {
guard numberOfLines > 0 else { return false }
return countLabelLines() > numberOfLines
}
}
you can make a category with UILabel
- (BOOL)isTextTruncated
{
CGRect testBounds = self.bounds;
testBounds.size.height = NSIntegerMax;
CGRect limitActual = [self textRectForBounds:[self bounds] limitedToNumberOfLines:self.numberOfLines];
CGRect limitTest = [self textRectForBounds:testBounds limitedToNumberOfLines:self.numberOfLines + 1];
return limitTest.size.height>limitActual.size.height;
}
It seems intrinsicContentSize will get the job done for labels with text set with attributedText and text. With that in mind, I think we can safely ditch all the bounding box bookkeeping and simplify as follows:
Swift 5.x
extension UILabel {
var isTruncated: Bool {
frame.width < intrinsicContentSize.width
}
var isClipped: Bool {
frame.height < intrinsicContentSize.height
}
}
To add to iDev 's answer, you should use intrinsicContentSize instead of frame, to make it works for Autolayout
- (BOOL)isTruncated:(UILabel *)label{
CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.intrinsicContentSize.width, CGFLOAT_MAX)
options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;
if (self.intrinsicContentSize.height < ceilf(sizeOfText.height)) {
return YES;
}
return NO;
}
Use this category to find if a label is truncated on iOS 7 and above.
// UILabel+Truncation.h
#interface UILabel (Truncation)
#property (nonatomic, readonly) BOOL isTruncated;
#end
// UILabel+Truncation.m
#implementation UILabel (Truncation)
- (BOOL)isTruncated
{
CGSize sizeOfText =
[self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{ NSFontAttributeName : label.font }
context: nil].size;
if (self.frame.size.height < ceilf(sizeOfText.height))
{
return YES;
}
return NO;
}
#end
This is it. This works with attributedText, before falling back to plain text, which makes a lot of sense for us folks who deal with multiple font families, sizes, and even NSTextAttachments!
Works fine with autolayout, but obviously the constraints must be defined and set before we check isTruncated, otherwise the label itself wont even know how to lay itself out, so no way it would even know if its truncated.
It doesnt work to approach this problem with just a plain NSString and sizeThatFits. Im not sure how people were getting positive results like that. BTW, as mentioned numerous times, using sizeThatFits is not ideal at all because it takes into account numberOfLines for the resulting size, which defeats the whole purpose of what we are trying to do, because isTruncated would always return false regardless if its truncated or not.
extension UILabel {
var isTruncated: Bool {
layoutIfNeeded()
let rectBounds = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
var fullTextHeight: CGFloat?
if attributedText != nil {
fullTextHeight = attributedText?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, context: nil).size.height
} else {
fullTextHeight = text?.boundingRect(with: rectBounds, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil).size.height
}
return (fullTextHeight ?? 0) > bounds.size.height
}
}
Here's the selected answer in Swift 3 (as an extension). The OP was asking about 1 line labels. Many of the swift answers I tried here are specific to multi-line labels and aren't flagging correctly on single line labels.
extension UILabel {
var isTruncated: Bool {
guard let labelText = text as? NSString else {
return false
}
let size = labelText.size(attributes: [NSFontAttributeName: font])
return size.width > self.bounds.width
}
}
This works for iOS 8:
CGSize size = [label.text boundingRectWithSize:CGSizeMake(label.bounds.size.width, NSIntegerMax) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : label.font} context:nil].size;
if (size.height > label.frame.size.height) {
NSLog(#"truncated");
}
I have written a category for working with UILabel's truncation. Works on iOS 7 and later. Hope it helps !
uilabel tail truncation
#implementation UILabel (Truncation)
- (NSRange)truncatedRange
{
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
textContainer.lineFragmentPadding = 0;
[layoutManager addTextContainer:textContainer];
NSRange truncatedrange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:0];
return truncatedrange;
}
- (BOOL)isTruncated
{
return [self truncatedRange].location != NSNotFound;
}
- (NSString *)truncatedText
{
NSRange truncatedrange = [self truncatedRange];
if (truncatedrange.location != NSNotFound)
{
return [self.text substringWithRange:truncatedrange];
}
return nil;
}
#end
extension UILabel {
public func resizeIfNeeded() -> CGFloat? {
guard let text = text, !text.isEmpty else { return nil }
if isTruncated() {
numberOfLines = 0
sizeToFit()
return frame.height
}
return nil
}
func isTruncated() -> Bool {
guard let text = text, !text.isEmpty else { return false }
let size: CGSize = text.size(withAttributes: [NSAttributedStringKey.font: font])
return size.width > self.bounds.size.width
}
}
You can calculate the width of the string and see if the width is greater than label width.
To add to what #iDev did, I modified the self.frame.size.height to use label.frame.size.height and also did not use NSStringDrawingUsesLineFontLeading. After those modifications, I achieved perfect calculation of when the truncation would happen (at least for my case).
- (BOOL)isTruncated:(UILabel *)label {
CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.bounds.size.width, CGFLOAT_MAX)
options: (NSStringDrawingUsesLineFragmentOrigin)
attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;
if (label.frame.size.height < ceilf(sizeOfText.height)) {
return YES;
}
return NO;
}
I had issues with boundingRect(with:options:attributes:context:) when using autolayout (to set a max height) and an attributed text with NSParagraph.lineSpacing
The spacing between lines was ignored (even when passed in attributes to the boundingRect method) so the label might be considered as not truncated when it was.
The solution I found is to use UIView.sizeThatFits :
extension UILabel {
var isTruncated: Bool {
layoutIfNeeded()
let heightThatFits = sizeThatFits(bounds.size).height
return heightThatFits > bounds.size.height
}
}
Make sure to call either of these in viewDidLayoutSubviews.
public extension UILabel {
var isTextTruncated: Bool {
layoutIfNeeded()
return text?.boundingRect(with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font!], context: nil).size.height ?? 0 > bounds.size.height
}
var isAttributedTextTruncated: Bool {
layoutIfNeeded()
return attributedText?.boundingRect(with: CGSize(width: bounds.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).size.height ?? 0 > bounds.size.height
}
}
SWIFT 5
Example for a multiple lined UILabel that is set to display only 3 lines.
let labelSize: CGSize = myLabel.text!.size(withAttributes: [.font: UIFont.systemFont(ofSize: 14, weight: .regular)])
if labelSize.width > myLabel.intrinsicContentSize.width * 3 {
// your label will truncate
}
Though the user may select the return key adding an extra line without adding to the "text width" in that case something like this may also be useful.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
// return pressed
}
}
In Swift 5.x
let size = label.text?.size(withAttributes: [NSAttributedString.Key.font: label.font!])
if size!.width > label.bounds.size.width {
debugPrint("Size increased", size?.width ?? 0, label.bounds.size.width, label.text ?? "")
}
Because all the answers above use depreciated methods, i thought this could be useful:
- (BOOL)isLabelTruncated:(UILabel *)label
{
BOOL isTruncated = NO;
CGRect labelSize = [label.text boundingRectWithSize:CGSizeFromString(label.text) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : label.font} context:nil];
if (labelSize.size.width / labelSize.size.height > label.numberOfLines) {
isTruncated = YES;
}
return isTruncated;
}
Using numberOfLines is not always useful because it may be zero and label can be truncated because of height constraints. Also, intrinsicContentSize didn't return the correct size in my case. You can use this extension:
extension UILabel {
var currentContentSize: CGSize {
layoutIfNeeded()
let myText = text! as NSString
let attributes: [NSAttributedString.Key: Any] = [.font: font!]
let labelRect = myText.boundingRect(with: CGSize(width: bounds.width,
height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: attributes,
context: nil)
return CGSize(width: labelRect.width, height: labelRect.height)
}
var isTruncated: Bool {
layoutIfNeeded()
return frame.width < currentContentSize.width || frame.height < currentContentSize.height
}
}
To handle iOS 6 (yes, some of us still have to), here's yet another expansion to #iDev's answer. The key takeaway is that, for iOS 6, to make sure your UILabel's numberOfLines is set to 0 before calling sizeThatFits; if not, it'll give you a result that says "the points to draw numberOfLines worth of height" is needed to draw the label text.
- (BOOL)isTruncated
{
CGSize sizeOfText;
// iOS 7 & 8
if([self.text respondsToSelector:#selector(boundingRectWithSize:options:attributes:context:)])
{
sizeOfText = [self.text boundingRectWithSize:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:self.font}
context:nil].size;
}
// iOS 6
else
{
// For iOS6, set numberOfLines to 0 (i.e. draw label text using as many lines as it takes)
// so that siteThatFits works correctly. If we leave it = 1 (for example), it'll come
// back telling us that we only need 1 line!
NSInteger origNumLines = self.numberOfLines;
self.numberOfLines = 0;
sizeOfText = [self sizeThatFits:CGSizeMake(self.bounds.size.width,CGFLOAT_MAX)];
self.numberOfLines = origNumLines;
}
return ((self.bounds.size.height < sizeOfText.height) ? YES : NO);
}
Swift 3 solution
I think the best solution is to (1) create a UILabel with the same properties as the label you're checking for truncation, (2) call .sizeToFit(), (3) compare the attributes of the dummy label with your actual label.
For example, if you want to check whether a one lined label that has varying width truncates or not, then you can use this extension:
extension UILabel {
func isTruncated() -> Bool {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: CGFloat.greatestFiniteMagnitude, height: self.bounds.height))
label.numberOfLines = 1
label.font = self.font
label.text = self.text
label.sizeToFit()
if label.frame.width > self.frame.width {
return true
} else {
return false
}
}
}
...but again, you can easily modify the above code to fit your needs. So let's say your label is multilined and has varying height. Then the extension would look something like this:
extension UILabel {
func isTruncated() -> Bool {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.font = self.font
label.text = self.text
label.sizeToFit()
if label.frame.height > self.frame.height {
return true
} else {
return false
}
}
}
Wouldnt it be easy to set the title attribute for the label , setting this will display full label when hovered.
you can calculate the length of the label and div width (convert to length - jQuery / Javascript - How do I convert a pixel value (20px) to a number value (20)).
set jquery to set title if length is greater than the div width.
var divlen = parseInt(jQuery("#yourdivid").width,10);
var lablen =jQuery("#yourlabelid").text().length;
if(lablen < divlen){
jQuery("#yourlabelid").attr("title",jQuery("#yourlabelid").text());
}
Related
Inconsistent behavior with number of actual lines calculation for UILabel in IOS with Swift
I am using the below UILabel extension method to calculate the number of actual lines in the UILabel. However, I see that it always returns more than the actual number of lines. Moreover, the excess number of lines it returns is not the same always. So, I cannot subtract it with a fixed constant before using the value. Any thoughts of what is wrong here. I have already looked at the solutions posted in stack overflow but they did not help either. extension UILabel { var maxNumberOfLines: Int { guard let text = text, let font = font else { return 0 } let charSize = font.lineHeight let textSize = (text as NSString).boundingRect( with: CGSize(width: frame.width, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil) let linesRoundedUp = Int(ceil(textSize.height/charSize)) return linesRoundedUp } }
Since I was trying to find the number of lines for UILabel in cell for row at index path delegate method, I was getting wrong result as the UILabel would not be laid out in the tableview cell yet. I used UIScreen.main.bounds - some offset in place of label width for number of lines calculation and that solved the issue. To be precise, I used extension UILabel { var maxNumberOfLines: Int { guard let text = text, let font = font else { return 0 } let charSize = font.lineHeight let textSize = (text as NSString).boundingRect( with: CGSize(width: UIScreen.main.bounds.width - SOMEOFFSET, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil) let linesRoundedUp = Int(ceil(textSize.height/charSize)) return linesRoundedUp } }
Default line spacing of UILabel
What is default line spacing in UILabel. I need to calculate how many lines can be in UILabel with set frame.
I'm not sure what the default line spacing is in a UILabel, but I used the below to calculate the number of lines a UILabel wants to be. extension UILabel { var preferredNumberOfLines: Int { guard let currentText = self.text else { return 0 } let labelSize = currentText.boundingRect(with: CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : self.font], context: nil) return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight)) } }
Setting the same font size for different labels AFTER scaling
I am making an app where I have 3 labels. I am using label auto-shrinking to help adapt the label's font size to the device. These labels are right next to each other, and that therefore means that I want them to have them the same font size. What currently happens is (because they have different amounts of text) they end up shrinking to different font sizes. Is there a way to make it so that after scaling, the label with the smallest font size is the standard font for all of the other labels. Thanks.
Programatically change UIlabel font size after dynamic resizing. See the example below. Calculate current font size with length of string & font. And then get minimum font size and apply separately for each UILabel override func viewWillAppear(_ animated: Bool) { let fontSize1 = self.label1.getFontSizeForLabel() let fontSize2 = self.label2.getFontSizeForLabel() let fontSize3 = self.label3.getFontSizeForLabel() let smallestFontSize = min(min(fontSize1, fontSize2), fontSize3) self.label1.font = self.label1.font.withSize(smallestFontSize) self.label2.font = self.label2.font.withSize(smallestFontSize) self.label3.font = self.label3.font.withSize(smallestFontSize) self.label1.adjustsFontSizeToFitWidth = false self.label2.adjustsFontSizeToFitWidth = false self.label3.adjustsFontSizeToFitWidth = false } UILabel Extension extension UILabel { func getFontSizeForLabel() -> CGFloat { let text: NSMutableAttributedString = NSMutableAttributedString(attributedString: self.attributedText!) text.setAttributes([NSAttributedStringKey.font: self.font], range: NSMakeRange(0, text.length)) let context: NSStringDrawingContext = NSStringDrawingContext() context.minimumScaleFactor = self.minimumScaleFactor text.boundingRect(with: self.frame.size, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: context) let adjustedFontSize: CGFloat = self.font.pointSize * context.actualScaleFactor return adjustedFontSize } } Storyboard Output
let labels = mainContainer .arrangedSubviews .compactMap { $0 as? UILabel } let newFontsSize = labels.map { label in var fontSize = label.font.pointSize while label.isTruncated { fontSize -= 0.25 label.font = label.font.withSize(fontSize) } return fontSize } if let smallestFontSize = newFontsSize.min() { labels.forEach { label in label.font = label.font.withSize(smallestFontSize) } } And the extension: extension UILabel { var isTruncated: Bool { frame.width < intrinsicContentSize.width } }
WKInterfacelabel check if truncated
I would like to check if the text in WKInterfacelabel is truncated. Since there is no text property and it seems I can not get the number of lines property that you can set in storyboard I wonder if there is any way or trick on how to achieve this!? Thanks !
You can get the number of lines that a label takes to display given text using below code. CGFloat labelWidth = 100.0f; NSString *text = #"some text"; [self.label setText:text]; [self.label setWidth:labelWidth]; UIFont *font = [UIFont systemFontOfSize:12.0f]; CGRect rect = [text boundingRectWithSize:CGSizeMake(labelWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName : font} context:nil]; CGFloat numOfLines = ceil(rect.size.height / font.lineHeight);
Swift version of #Sahana's answer above func isTruncated(text: String, width: CGFloat, font: UIFont, numOfLines: Int) -> Bool { let rect = text.boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : font], context: nil) let thisNumOfLines = Int(ceil(rect.size.height / font.lineHeight)) let isTruncated = thisNumOfLines > numOfLines return isTruncated }
How to find actual number of lines of UILabel?
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,...
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 } }
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)
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);
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.
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().
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 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)")
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 } }
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); }
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
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);
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 } }
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!!!
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
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)) } }
⚠️ 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.