Multiline UILabel without word wrap?

2019-04-09 22:08发布

问题:

Is it possible to have a UILabel that consists of multiple \n-delimited lines to have lines with their width > the label width to be truncated and not wrapped?

Suppose I have some text like the following:

  1. This is a really long first line of text that is too long to fit horizontally
  2. Short line
  3. Another short line

I want this to appear in my UILabel like so:

1. This is a really long first line of text...
2. Short line
3. Another short line

However what is happening is I'm getting this:

1. This is a really long first line of text  
that is too long to fit horizontally
2. Short line...

The third line is getting cut off. I've set the number of lines to 3, but it is still wrapping the first long line. It doesn't seem to matter what I set the line breaks property to on the label--it always wraps that first line. Is there any way to prevent wrapping completely on the label?

回答1:

I don't think this is possible with any setting you can apply to your label. One way to do it, is to break the string into its individual lines, truncate any line that needs it so that it (with an added ellipsis) fits on one line, then put the string back together with line breaks. Something like this should work for any number of lines,

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (nonatomic) CGFloat ellipsisWidth;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *text = @"This is a really long first line of text that is too long to fit horizontally\nShort line\nAnother short line";
    NSString *ellipsis = @"...";
    self.ellipsisWidth = [ellipsis sizeWithAttributes:@{NSFontAttributeName:self.label.font}].width;

    __block NSMutableString *truncatedString = [@"" mutableCopy];
    [text enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
        [truncatedString appendFormat:@"%@\n", [self oneLineOfString:line withFont:self.label.font]];
    }];
    NSString *finalString = [truncatedString stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
    self.label.numberOfLines = 0;
    self.label.text = finalString;
}

-(NSString *)oneLineOfString:(NSString *) aLine withFont:(UIFont *) font {
    __block NSString *singleLine = nil;
    __block NSString *lastFragment;

    [aLine enumerateSubstringsInRange:NSMakeRange(0, aLine.length) options:NSStringEnumerationByWords usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
        NSString *textFragment = [aLine substringToIndex:(substringRange.location + substringRange.length)];
        CGRect textRect = [textFragment boundingRectWithSize:CGSizeMake(CGFLOAT_MAX ,CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil];
        if (textRect.size.width >= self.label.bounds.size.width - self.ellipsisWidth) {
            singleLine = [lastFragment stringByAppendingString:@"..."];
            *stop = YES;
        }
        lastFragment = textFragment;
    }];
    if (!singleLine) singleLine = aLine; // it doesn't need to be truncated, so return the passed in line
    return singleLine;
}

If you want to truncate by character instead of by word, you can pass NSStringEnumerationByComposedCharacterSequences instead of NSStringEnumerationByWords to the options parameter of enumerateSubstringsInRange:options:usingBlock:.

Of course, you could do it the easy way; stack 3 labels on top of each other, and give each one a line of your text :)