How to check if UILabel is truncated?
Asked Answered
D

23

126

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.

Daphne answered 19/6, 2010 at 20:25 Comment(3)
Have a look to the solution based on lines count I posted hereCarnassial
Can't believe after all these years Apple still hasn't incorporated something as basic as this in to the UILabel API.Neomaneomah
@Neomaneomah - it's just insanity.Upon
R
121

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) {
   ...
}
Relief answered 19/6, 2010 at 20:34 Comment(5)
Thanks, this is exactly what I needed. The only difference was, sizeWithFont: returns a CGSize.Daphne
Ah, thanks for pointing that out, I've corrected the sample code.Relief
sizeWithFont is deprecated use: [label.text sizeWithAttributes:@{NSFontAttributeName : label.font}];Tokay
@fatuhoku numberOfLines returns the maximum number of lines used to display the text as outlined in the UILabel class reference: developer.apple.com/library/ios/documentation/UIKit/Reference/…Dunkle
if label has number of line try multiply width with number of line like this if (size.width > label.bounds.size.width*label.numberOfLines) { ... }Turnspit
F
120

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
    }

}
Fumigant answered 16/7, 2015 at 16:29 Comment(6)
replace height of 999999.0 with CGFloat(FLT_MAX)Birdiebirdlike
for swift 3 i would use CGFloat.greatestFiniteMagnitudeArnone
nice answer but its better to use calculated property instead of func : var isTruncated: Bool { if let string = self.text { let size: CGSize = (string as NSString).boundingRect( with: CGSize(width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: self.font], context: nil).size return (size.height > self.bounds.size.height) } return false }Pancratium
This didn't work for me because I was using an NSAttributedString. To get it to work, I needed to modify the attributes value to use: attributedText?.attributes(at: 0, effectiveRange: nil)Fain
Great answer, had to change it a bit for my requirements, but worked perfectly. I was also using an attributed string - so for attributes I used: (attributedText?.attributes(at: 0, effectiveRange: nil) ?? [.font: font]), just make sure to check whether the labelText is not empty when using this solution.Jarboe
you may need to call layoutIfNeeded either on the label or within the code of isTruncated depending on when you are checking thisPunk
S
21

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");
}
Spencerspencerian answered 7/11, 2012 at 15:38 Comment(0)
D
19

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
    }
}
Disproportionation answered 10/10, 2021 at 2:47 Comment(0)
C
16

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
    }
}
Carnassial answered 24/3, 2017 at 14:27 Comment(1)
This is the nice answer for swift. Thanks.Outpoint
U
14

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;
}
Uela answered 26/10, 2012 at 4:41 Comment(5)
from the doc : textRectForBounds:limitedToNumberOfLines: "You should not call this method directly"...Spencerspencerian
@Spencerspencerian thank you,I see that,the implement here is limited.but when you call this method after setting bounds and text, it will work wellUela
Why do you +1 when setting the limitedToNumberOfLines?Pul
@Pul to check if the label is higher when it is allowed more room for the text.Cubit
Thanks for this code, it worked for me apart from some border cases when using auto layout. I fixed them by adding: setNeedsLayout() layoutIfNeeded() at the beginning of the method.Sleeve
M
10

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
Mackey answered 1/7, 2014 at 10:37 Comment(0)
L
10

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;
}
Layette answered 22/10, 2014 at 14:34 Comment(2)
Thanks! Usage of intrinsicContentSize instead of frame was the solution to my problem when UILabel height is actually enough to fit the text, but it has limited number of lines and thus is still truncating.Boeotian
For some reason, this is returning NO even when the text doesn't fit into the labelUniformize
D
6

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
    }
}
Dimorphism answered 31/10, 2018 at 16:24 Comment(4)
this working fine for me thanks !!are there any updates or modification for this?Mongo
Great answer. The only issue is attributedText is never nil, even when you don't set it. So, I implemented this as two vars isTextTruncated and isAttributedTextTruncated.Thistly
@Harris it says default is nil in the uilabel.h fileDimorphism
@LucasChwe I know, but it's not true in practice. You can try it for yourself and see. fyi, forums.developer.apple.com/thread/118581Thistly
C
4

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
    }
}
Carmencarmena answered 9/5, 2018 at 20:11 Comment(2)
Axel's answer didn't work for this. This one did. Thanks!Cerebritis
does intrinsicContentSize.width work for both text and attributedText?Disproportionation
D
2

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");
}
Dna answered 17/12, 2014 at 0:8 Comment(0)
D
2

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
Distichous answered 13/6, 2015 at 0:31 Comment(1)
every time it gives NSNotFound onlyCounterfeit
A
2
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.

Abeyant answered 14/5, 2018 at 19:38 Comment(0)
U
1

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;
}
Unlay answered 9/10, 2014 at 17:51 Comment(0)
N
1

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
    }
}
Nadanadab answered 4/6, 2018 at 9:36 Comment(0)
T
1

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
    }

}
Thistly answered 30/9, 2019 at 22:15 Comment(0)
I
1

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 

    }
}
Isthmian answered 12/2, 2020 at 0:11 Comment(0)
M
1

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 ?? "")
}
Marion answered 24/6, 2021 at 13:51 Comment(0)
Z
0

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;
}
Zetana answered 23/4, 2014 at 9:3 Comment(3)
You are dividing width by height and if it is bigger than number of line (which might very well be 0) you are saying label is truncated. There is no way this works.Henchman
@CanLeloğlu please test it. It is a working example.Zetana
What if the numberOfLines is equal to zero?Henchman
C
0

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
    }
}
Cramp answered 22/1, 2023 at 12:50 Comment(0)
S
-1

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);
}
Sandstrom answered 30/3, 2015 at 19:20 Comment(0)
A
-1

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
        }
    }
}
Arman answered 26/1, 2017 at 10:31 Comment(0)
L
-6

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());
}
Lithium answered 4/2, 2016 at 14:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.