5

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

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

Ben Packard
  • 26,102
  • 25
  • 102
  • 183

5 Answers5

4

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
    }
}
Claus
  • 5,662
  • 10
  • 77
  • 118
2

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

Nekto
  • 17,837
  • 1
  • 55
  • 65
  • I can see how that might help identify if the label will be truncated. But I don't think it helps me for example 4? Thanks for the link by the way. – Ben Packard Oct 18 '12 at 02:08
  • What you mean? It will completely solve your problem for any cases. – Nekto Oct 18 '12 at 02:16
  • You said: `only when it will cause a change in either truncation or line break positions`. I give you even code how to check this. – Nekto Oct 18 '12 at 02:17
  • I'm sorry, I meant example 1. There is no truncation applied in example one - only a change in line breaks. I will test whether sizeWithFont:... catches this right now. – Ben Packard Oct 18 '12 at 02:20
  • I tested, and as suspected this does not work for example one. The text is not truncated, only the line breaks change, so it slips through the net. – Ben Packard Oct 18 '12 at 02:26
  • Yeah, it won't catch changes in line breaks. But I am not sure that you could easily get that. Only by using CoreText I believe by drawing text line by line and checking how much text was printed and then comparing with label's layout. – Nekto Oct 18 '12 at 02:30
  • Updated my answer. Check it =) – Nekto Oct 18 '12 at 02:36
  • Thanks, but I already have that part working. The test is what I need to solve. Mail.app finds a way to detect line breaks. – Ben Packard Oct 18 '12 at 03:43
  • Mail.app is doing by replacing `UILabel` with animation =) – Nekto Oct 18 '12 at 04:13
  • @BenPackard if you couldn't do these 4 steps and see that it is works in the same style like Mail.app then I could not help you anymore. Good luck – Nekto Oct 18 '12 at 19:01
1

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;
}
Raz
  • 2,633
  • 1
  • 24
  • 44
  • How does the ratio of the label size relate to the numberOfLines? I can't see how this works (it certainly didn't when I tested it out) – aaroncatlin Apr 10 '16 at 11:24
1

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;
    }
Rajesh
  • 10,318
  • 16
  • 44
  • 64
0

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
Community
  • 1
  • 1
hsusmita
  • 282
  • 3
  • 16