How to check if UILabel is truncated?

IosObjective CIphoneUilabelTruncate

Ios Problem Overview


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.

Ios Solutions


Solution 1 - Ios

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

Solution 2 - Ios

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
    }

}

Solution 3 - Ios

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

Solution 4 - Ios

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

Solution 5 - Ios

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

Solution 6 - Ios

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

Solution 7 - Ios

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

Solution 8 - Ios

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

Solution 9 - Ios

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

Solution 10 - Ios

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

Solution 11 - Ios

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

Solution 12 - Ios

I have written a category for working with UILabel's truncation. Works on iOS 7 and later. Hope it helps ! https://stackoverflow.com/questions/8985520/uilabel-tail-truncation#30813691

@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

Solution 13 - Ios

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.

Solution 14 - Ios

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

Solution 15 - Ios

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

Solution 16 - Ios

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
    }

}

Solution 17 - Ios

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

Solution 18 - Ios

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

Solution 19 - Ios

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

Solution 20 - Ios

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

Solution 21 - Ios

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

Solution 22 - Ios

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 - https://stackoverflow.com/questions/3024084/jquery-javascript-how-do-i-convert-a-pixel-value-20px-to-a-number-value-2).

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

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
QuestionRandallView Question on Stackoverflow
Solution 1 - IosprogrmrView Answer on Stackoverflow
Solution 2 - IosRobinView Answer on Stackoverflow
Solution 3 - IosMartinView Answer on Stackoverflow
Solution 4 - IosClausView Answer on Stackoverflow
Solution 5 - IosDongXuView Answer on Stackoverflow
Solution 6 - IosAdrianView Answer on Stackoverflow
Solution 7 - IosRajeshView Answer on Stackoverflow
Solution 8 - Iosonmyway133View Answer on Stackoverflow
Solution 9 - IosLucas ChweView Answer on Stackoverflow
Solution 10 - IosTravis M.View Answer on Stackoverflow
Solution 11 - IosMarcio FonsecaView Answer on Stackoverflow
Solution 12 - IosAlejandro CotillaView Answer on Stackoverflow
Solution 13 - IosHayk BrsoyanView Answer on Stackoverflow
Solution 14 - IoskgaidisView Answer on Stackoverflow
Solution 15 - IosAxel GuilminView Answer on Stackoverflow
Solution 16 - IosElijahView Answer on Stackoverflow
Solution 17 - IosWaylan SandsView Answer on Stackoverflow
Solution 18 - IosNareshView Answer on Stackoverflow
Solution 19 - IosRazView Answer on Stackoverflow
Solution 20 - IosJohn JaceckoView Answer on Stackoverflow
Solution 21 - IosSaoud RizwanView Answer on Stackoverflow
Solution 22 - Iosuser3405326View Answer on Stackoverflow