How to tell when a UILabel will be truncated an/or its line break position will change

I have a multi-line UILabel (numberOfLines = 0). It’s width can change at runtime, and sometimes this leads to truncation and/or re-wrapping. Some examples illustrate this best:

Example 1: the reduction in width leads to a different line break point

  • iOS 7 Custom TableView Is Under TabBar
  • Is UIView's opaque property with a value of YES in conflict with its backgroundColor property with a value of ?
  • How can I center a UIView programmatically on top of an existing UIView using Autolayout?
  • UIViewController - mysteriously slow to load
  • How do I add a radial gradient to a UIView?
  • On iOS, if a superview's userInteractionEnabled is NO, then all subviews are disabled as well?
  • enter image description here
    enter image description here

    Example 2: the reduction in width leads to truncation

    enter image description here
    enter image description here

    Example 3: the reduction in width leads to both truncation and a different line break position

    enter image description here
    enter image description here

    Example 4: the reduction in width does not have any effect on truncation or line break position

    enter image description here
    enter image description here

    Since this change in formatting can be quite jarring, I intend to mask it behind some animation (probably a fade in/fade out). However, the first hurdle is identifying when I need to do this. I don’t want to apply the animation whenever the label re-sizes – only when it will cause a change in either truncation or line break positions.

    How might I test this? The test should return YES for example 1, 2, and 3, but NO for example 4.

    Note: the resizing will never alter the number of lines in my example.

    Note 2: if anyone has some better tags related to text formatting I’d love to know them – feel free to edit.

    Note 3: if you are interested in seeing this behavior accomplished, try Apple’s mail.app on the iPhone. When viewing the inbox, swipe an email and watch the summary line fade-in/out as it re-wraps and/or truncates (but not when it doesn’t need to).

    5 Solutions Collect From Internet About “How to tell when a UILabel will be truncated an/or its line break position will change”

    You could know the size of label that is needed to display a particular NSString instance. For example, you could use that one:

    - (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode

    Returns the size of the string if it were rendered with the specified constraints.

    So what you want is to get CGSize for a particular string and check if it is not larger then UILabel size:

        UILabel *label;
        CGSize sizeNeeded = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(label.bounds.size.width, MAXFLOAT)];
        if (sizeNeeded.height > label.bounds.size.height)
        {
            NSLog(@"String is truncated");
        }
    

    More useful NSString methods you could find here: NSString UIKit Additions Reference

    Ok, another way of doing what you want:

    1) Create 2 UILabel with the same properties but second one (label2) will be with another one width.

    2) Set alpha property of label2 to 0.0 in non-edit mode.

    3) When edit mode begins make such animation:

    // label1.alpha == 1.0, label2.alpha == 0.0 
    {[UIView animateWithDuration:0.5 animations:^{
        label1.alpha = 0.0;
        label2.alpha = 1.0;
    }];
    

    4) When edit mode ends:

    {[UIView animateWithDuration:0.5 animations:^{
        label1.alpha = 1.0;
        label2.alpha = 0.0;
    }];
    

    That will give you the same result as in Mail.app

    Swift 3 solution

    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 {
    
            if (self.countLabelLines() > self.numberOfLines) {
                return true
            }
            return false
        }
    }
    

    The answer above is using a depreciated method, so i thought the following code 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;
    }
    

    Use this method to find lable truncated in iOS 7.

    - (BOOL)isTruncated:(UILabel *)label{
            CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake(label.bounds.size.width, CGFLOAT_MAX)
                                                         options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                                      attributes: [NSDictionary dictionaryWithObject:label.font forKey:NSFontAttributeName] context: nil].size;
    
            if (self.frame.size.height < ceilf(sizeOfText.height)) {
                return YES;
            }
            return NO;
        }
    

    For versions higher than iOS 7, you can check the following solutions:

    1. https://stackoverflow.com/a/30813691/2123122
    2. https://stackoverflow.com/a/32094824/2123122