Строки, отсутствующие в высоком UILabel при встраивании NSTextAttachment

Я могу создать многострочный NSAttributedString, используя экранированные символы новой строки (@"\n"). В iOS 7 теперь я могу вставлять UIImage в строки с атрибутами (через NSTextAttachment).

Я заметил, что всякий раз, когда я устанавливаю attributedText для UILabel в многострочную атрибутивную строку со встроенным изображением, количество фактически отображаемых строк обратно пропорционально высоте метки. Например, когда высота метки равна 80, появляются две строки; когда высота около 100, появляется только вторая строка; когда высота около 130 ничего не появляется.

Эта проблема возникла при попытке расположить несколько UILabels рядом внутри UITableViewCell и при этом метки увеличивались (по вертикали) с высотой ячейки.

Кто-нибудь может объяснить, почему это происходит? Кто-нибудь знает обходные пути, не связанные с уменьшением UILabel?


Образец кода:

@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

... Поместите это в контроллер представления по умолчанию «Приложения с одним представлением». (Вы можете выбрать свое изображение.)


person oo-di-lolly    schedule 08.10.2013    source источник
comment
Похоже, что эта ошибка будет исправлена ​​в iOS 7.1.   -  person matt    schedule 25.11.2013
comment
@matt Можете ли вы подтвердить исправление?   -  person EthanB    schedule 09.04.2014
comment
@EthanB Да, похоже, это было просто легально CYA.   -  person matt    schedule 09.04.2014


Ответы (4)


Это действительно не имеет ничего общего с NSTextAttachment. Дело в том, что в выпущенной до сих пор iOS 7 UILabel не очень хорошо рисует строки с атрибутами. Простая строка атрибутов с некоторым подчеркиванием и центрированным стилем абзаца будет отображаться пустой или частично пустой в UILabel; та же строка с атрибутами отлично отображается в UITextView.

Итак, одно из решений на данный момент: используйте вместо этого UITextView. На самом деле это довольно хорошее решение, потому что в iOS 7 UITextView является просто оболочкой стека Text Kit. Таким образом, он рисует атрибутированную строку простым способом. Этому не мешает скрытая связь с Web Kit, которая была в предыдущих версиях iOS.

С другой стороны, я также нашел обходной путь для этой ошибки UILabel; вам нужно возиться с количеством строк метки и строки таким образом, чтобы зажать текст до верхней части этикетки: см. мой ответ здесь - https://stackoverflow.com/а/19409962/341994

Или вы можете просто подождать, пока Apple исправит ошибку, и держать пальцы скрещенными. РЕДАКТИРОВАТЬ: в iOS 7.1 кажется, что ошибка будет исправлена, и обходной путь не потребуется.

person matt    schedule 16.10.2013
comment
Потратил несколько часов, пытаясь понять, почему UILabel отлично работает в симуляторе и на большинстве устройств, но не на клиентском устройстве. Оказалось, что они были еще на iOS 7.0, поэтому столкнулся с этой ошибкой. Переход на UITextView работал отлично. - person NigelG; 07.06.2014
comment
Я не вижу исправления в iOS 7.1. Можете ли вы поделиться с нами, где вы слышали, что это произойдет? - person michaelsnowden; 16.07.2014
comment
Пример @doctordoder: github.com/ mattneub/Programming-iOS-Book-Examples/blob/master/ Добавьте метку и поместите окончательную строку с атрибутами в метку вместо текстового представления. В 7.0 метка пуста. В 7.1 работает отлично. - person matt; 16.07.2014

Кажется, это ошибка в UILabel. Тот же код отлично работает, когда вместо UILabel используется UITextView (размер шрифта по умолчанию для UITextView отличается, поэтому я тестировал его на разных высотах).

person Arek Holko    schedule 12.10.2013
comment
@EthanB: нет, я потратил на это некоторое время, но безуспешно (за исключением использования вместо этого UITextView). Эти API новые, поэтому, возможно, Apple недостаточно их протестировала. - person Arek Holko; 15.10.2013

Я нашел другой обходной путь для этой ошибки, который достаточно отличается от моего предыдущего ответа, поэтому я предоставляю его как еще один ответ: пусть метка устанавливает свою собственную высоту.

В этом коде я удаляю ограничение по высоте из метки с фиксированным ограничением по ширине и заменяю его ограничением по высоте (и я уверен, что есть другие способы добиться того же результата):

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

Эта метка правильно отображает каждую строку с атрибутами, которую я набрасываю! Конечно, вы теряете автоматическое центрирование строки по вертикали, но в этом и весь источник бага, так что потерять его не так уж и страшно.

person matt    schedule 23.10.2013

Вы пытались получить фактическую высоту текста, используя метод boundingRectWithSize:

NSAttributedString *text;
CGFloat width = 200;
CGRect rect = [text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
CGFloat height = rect.size.height;
person cescofry    schedule 15.10.2013
comment
Если у вас фиксированная ширина, ограничивающая высота текста не должна меняться при увеличении метки, верно? - person EthanB; 15.10.2013
comment
КАК вы растите текст да. Вы достигаете этого, устанавливая высоту ограничивающего размера в CGFLOAT_MAX. - person cescofry; 15.10.2013
comment
ОП спросил, почему изменения высоты UILabel повлияли на видимые строки текста. См. пример кода. Для данного неизменного NSAttributedString ограничивающая высота (ваша rect.size.height) не должна зависеть от высоты UILabel. - person EthanB; 15.10.2013