Pregunta

I can create a multi-line NSAttributedString by using escaped new-line characters (@"\n"). With iOS 7, I can now embed a UIImage inside attributed strings (via NSTextAttachment).

I have noticed that whenever I set the attributedText of a UILabel to a multi-line attributed string with an embedded image, the number of lines actually displayed is inversely proportional to the height of the label. For example, when the height of the label is 80, two lines appear; when the height is around 100, only the second line appears; when the height is about 130, nothing appears.

This problem occurred while trying to position multiple UILabels side-by-side inside a UITableViewCell and having the labels grow (vertically) with the cell height.

Can anyone explain why this is happening? Does anyone know workarounds that don't involve making the UILabel smaller?


Sample code:

@implementation SOViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSMutableAttributedString *text1 = [[NSMutableAttributedString alloc] init];
    [text1 appendAttributedString:[[NSAttributedString alloc] initWithString:@"Line 1\n"]];
    [text1 appendAttributedString:[[NSAttributedString alloc] initWithString:@"Line 2"]];

    UIImage *image = [UIImage imageNamed:@"17x10"]; //some PNG image (17px by 10px)

    NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
    attachment.image = image;
    attachment.bounds = CGRectMake(0, 0, image.size.width, image.size.height);

    NSMutableAttributedString *text2 = [[NSMutableAttributedString alloc] init];
    [text2 appendAttributedString:[[NSAttributedString alloc] initWithString:@"Line 1\n"]];
    [text2 appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
    [text2 appendAttributedString:[[NSAttributedString alloc] initWithString:@"Line 2"]];

    CGFloat margin = 20;

    //shows both lines when height == 80
    //shows line 2 when 90 <= height <= 120
    //shows nothing when height == 130
    CGFloat height = ???;
    CGFloat width = 200;

    UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectMake(margin, margin, width, height)];
    UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectMake(margin, margin + height, width, height)];
    [self.view addSubview:label1];
    [self.view addSubview:label2];
    label1.backgroundColor = [UIColor orangeColor];
    label2.backgroundColor = [UIColor blueColor];
    label2.textColor = [UIColor whiteColor];
    label1.numberOfLines = 0;
    label2.numberOfLines = 0;
    label1.attributedText = text1;
    label2.attributedText = text2;

    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    imageView.frame = CGRectMake(margin + width, margin + height, image.size.width, image.size.height);
    [self.view addSubview:imageView];
}

@end

... Put this in the default view controller of a "Single View Application". (You can pick your own image.)

¿Fue útil?

Solución

It really has nothing to do with the NSTextAttachment. It's that in iOS 7 as released so far, UILabel is not very good at drawing attributed strings. A simple attributed string with some underlining and a centered paragraph style will show up blank, or partially blank, in a UILabel; the same attributed string draws fine in a UITextView.

So, one solution for now is: Use UITextView instead. This is actually a pretty good solution because, in iOS 7, UITextView is just a wrapper around the Text Kit stack. So it is drawing the attributed string in a straightforward way. It is not hampered by the under-the-hood relationship to Web Kit that it had in previous iOS versions.

On the other hand, I have also found a workaround for this UILabel bug; you have to fiddle with the number of lines of the label and the string in such a way as to jam the text up to the top of the label: see my answer here - https://stackoverflow.com/a/19409962/341994

Or you could just wait for Apple to fix the bug and keep your fingers crossed. EDIT: In iOS 7.1, it appears that the bug will be fixed and no workaround will be needed.

Otros consejos

It seems to be a bug in UILabel. The same code works fine when UITextView is used instead of UILabel (UITextView's default font size is different, so I tested it on different heights).

I have found another workaround for this bug, which is sufficiently different from my previous answer that I'm providing it as another answer: let the label set its own height.

In this code, I'm removing the height constraint from a label with a fixed width constraint, and replacing it with a greater-than height constraint (and I'm sure there are other ways to achieve the same outcome):

[self.lab removeConstraint:self.labelHeight];
[self.lab addConstraint:
 [NSLayoutConstraint constraintWithItem:self.lab 
 attribute:NSLayoutAttributeHeight 
 relatedBy:NSLayoutRelationGreaterThanOrEqual 
 toItem:nil attribute:0 multiplier:1 constant:20]];

That label displays correctly every attributed string I throw at it! Of course, you lose the automatic vertical centering of the string, but that's the whole source of the bug, so losing it is not so terrible.

Did you try to get the actually height of the text using the boundingRectWithSize method:

NSAttributedString *text;
CGFloat width = 200;
CGRect rect = [text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
CGFloat height = rect.size.height;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top