iOS AutoLayout multi-line UILabel


следующий вопрос является своего рода продолжением этого:

iOS: многострочный UILabel в Auto Layout

основная идея заключается в том, что каждое представление должно указывать его "предпочтительный" (внутренний) размер, чтобы AutoLayout мог знать, как правильно его отображать. UILabel-это просто пример ситуации, когда вид не может сам по себе знаю, какой размер ему нужен для отображения. Это зависит от того, какая ширина указана.

Как mwhuss указал, setPreferredMaxLayoutWidth сделал трюк, чтобы сделать ярлык охватывать несколько строк. Но это не главный вопрос. Вопрос в том, где и когда я получу этот ширина значение, которое я посылаю в качестве аргумента setPreferredMaxLayoutWidth.

мне удалось сделать что-то, что выглядит нормально, так что поправьте меня, если я ошибаюсь в любом случае и скажите, пожалуйста, если вы знаете лучший способ.

в Наследник UIView это

-(CGSize) intrinsicContentSize

Я setPreferredMaxLayoutWidth для моих UILabels согласно собственной личности.рамка.ширина.

UIViewController

-(void) viewDidLayoutSubviews

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

9 59

9 ответов:

кажется раздражающим, что UILabel не имеет по умолчанию своей ширины для предпочтительной максимальной ширины макета, если у вас есть ограничения, которые однозначно определяют эту ширину для вас.

почти в каждом отдельном случае, когда я использовал метки в Autolayout, предпочтительной максимальной шириной макета была фактическая ширина метки, как только была выполнена остальная часть моего макета.

Итак, чтобы это произошло автоматически, я использовал подкласс UILabel, который переопределяет setBounds:. Вот, назовем супер реализацию, тогда,если это уже не случай, установите предпочтительную максимальную ширину макета, чтобы быть шириной размера границ.

акцент важен-установка предпочтительного максимального макета приводит к выполнению другого прохода макета, поэтому вы можете получить бесконечный цикл.

есть ответ на этот вопрос на objc.io в разделе "внутренний размер содержимого многострочного текста"Advanced Auto Layout Toolbox. Вот соответствующая информация:

внутренний размер содержимого UILabel и NSTextField неоднозначен для многострочного текста. Высота текста зависит от ширины линии, которая пока не определена при решении проблем. Чтобы решить эту проблему, оба класса имеют новый свойство называется preferredMaxLayoutWidth, которое задает максимальную ширину строки для вычисления внутреннего размера содержимого.

поскольку мы обычно не знаем это значение заранее, нам нужно принять двухэтапный подход, чтобы получить это право. Сначала мы позволяем Auto Layout делать свою работу, а затем мы используем полученный кадр в проходе макета, чтобы обновить предпочтительную максимальную ширину и снова запустить макет.

код, который они дают для использования внутри вид контроллер:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
    [self.view layoutIfNeeded];
}

взгляните на их пост, там больше информации о том, почему это необходимо сделать макет дважды.

обновление

мой оригинальный ответ кажется полезным, поэтому я оставил его нетронутым ниже, однако в моих собственных проектах я нашел более надежное решение, которое работает вокруг ошибок в iOS 7 и iOS 8. https://github.com/nicksnyder/ios-cell-layout

оригинальный ответ

это полное решение, которое работает для меня на iOS 7 и iOS 8

цель C

@implementation AutoLabel

- (void)setBounds:(CGRect)bounds {
  if (bounds.size.width != self.bounds.size.width) {
    [self setNeedsUpdateConstraints];
  }
  [super setBounds:bounds];
}

- (void)updateConstraints {
  if (self.preferredMaxLayoutWidth != self.bounds.size.width) {
    self.preferredMaxLayoutWidth = self.bounds.size.width;
  }
  [super updateConstraints];
}

@end

Свифт

import Foundation

class EPKAutoLabel: UILabel {

    override var bounds: CGRect {
        didSet {
            if (bounds.size.width != oldValue.size.width) {
                self.setNeedsUpdateConstraints();
            }
        }
    }

    override func updateConstraints() {
        if(self.preferredMaxLayoutWidth != self.bounds.size.width) {
            self.preferredMaxLayoutWidth = self.bounds.size.width
        }
        super.updateConstraints()
    }
}

У нас была ситуация, когда авто-layouted UILabel внутри UIScrollView выложил отлично в портрете, но при повороте на пейзаж высота UILabel не была пересчитана.

мы обнаружили, что ответ от @jrturton исправил это, предположительно потому, что теперь preferredMaxLayoutWidth правильно установлен.

вот код, который мы использовали. Просто установите пользовательский класс из построителя интерфейса CVFixedWidthMultiLineLabel.

CVFixedWidthMultiLineLabel.h

@interface CVFixedWidthMultiLineLabel : UILabel

@end 

CVFixedWidthMultiLineLabel.м

@implementation CVFixedWidthMultiLineLabel

// Fix for layout failure for multi-line text from
// http://stackoverflow.com/questions/17491376/ios-autolayout-multi-line-uilabel
- (void) setBounds:(CGRect)bounds {
    [super setBounds:bounds];

    if (bounds.size.width != self.preferredMaxLayoutWidth) {
        self.preferredMaxLayoutWidth = self.bounds.size.width;
    }
}

@end

используя boundingRectWithSize

я решил свою борьбу с двумя многострочными ярлыками в наследство UITableViewCell это было использование "\n " в качестве разрыва строки путем измерения желаемой ширины следующим образом:

- (CGFloat)preferredMaxLayoutWidthForLabel:(UILabel *)label
{
    CGFloat preferredMaxLayoutWidth = 0.0f;
    NSString *text = label.text;
    UIFont *font = label.font;
    if (font != nil) {
        NSMutableParagraphStyle *mutableParagraphStyle = [[NSMutableParagraphStyle alloc] init];
        mutableParagraphStyle.lineBreakMode = NSLineBreakByWordWrapping;

        NSDictionary *attributes = @{NSFontAttributeName: font,
                                     NSParagraphStyleAttributeName: [mutableParagraphStyle copy]};
        CGRect boundingRect = [text boundingRectWithSize:CGSizeZero options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
        preferredMaxLayoutWidth = ceilf(boundingRect.size.width);

        NSLog(@"Preferred max layout width for %@ is %0.0f", text, preferredMaxLayoutWidth);
    }


    return preferredMaxLayoutWidth;
}

тогда вызов метода был тогда так же прост, как:

CGFloat labelPreferredWidth = [self preferredMaxLayoutWidthForLabel:textLabel];
if (labelPreferredWidth > 0.0f) {
    textLabel.preferredMaxLayoutWidth = labelPreferredWidth;
}
[textLabel layoutIfNeeded];

поскольку мне не разрешено добавлять комментарий, я обязан добавить его в качестве ответа. Версия jrturton работала только для меня, если я позвоню layoutIfNeeded на updateViewConstraints до получения preferredMaxLayoutWidth соответствующей метки. Без звонка в layoutIfNeeded the preferredMaxLayoutWidth всегда 0 на updateViewConstraints. И все же, он всегда имел желаемое значение при регистрации setBounds:. Мне не удалось узнать, когда правильно preferredMaxLayoutWidth был установлен. Я переопределяю setPreferredMaxLayoutWidth: на UILabel подкласс, но он никогда не получал называемый.

Кратко, Я:

  • ...sublcassed UILabel ни
  • ...и переопределить setBounds: в, если еще не установлен, set preferredMaxLayoutWidth до CGRectGetWidth(bounds)
  • ...звоните [super updateViewConstraints] до следующего
  • ...звоните layoutIfNeeded перед preferredMaxLayoutWidth для использования в расчете размера этикетки

EDIT: этот обходной путь только кажется, что работает, или необходим, иногда. У меня просто была проблема (iOS 7/8) где высота этикетки не была правильно рассчитана, как preferredMaxLayoutWidth вернулся 0 после того, как процесс компоновки был выполнен один раз. Так что после некоторых проб и ошибок (и найдя это запись в блог) я переключился на использование UILabel снова и просто установите верхние, нижние, левые и правые ограничения автоматической компоновки. И по какой-то причине высота метки была установлена правильно после обновления текста.

как предложил другой ответ, я попытался переопределить viewDidLayoutSubviews:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    _subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40;
    [self.view layoutIfNeeded];
}

это сработало, но оно было видно на пользовательском интерфейсе и вызвало "видимое мерцание", т. е. сначала метка была отображена с высотой двух строк, затем она была повторно отображена с высотой только одной строки.

Это было неприемлемо для меня.

Я нашел тогда лучшее решение путем переопределения updateViewConstraints:

-(void)updateViewConstraints {
    [super updateViewConstraints];

    // Multiline-Labels and Autolayout do not work well together:
    // In landscape mode the width is still "portrait" when the label determines the count of lines
    // Therefore the preferredMaxLayoutWidth must be set
    _subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40;
}

Это было лучшее решение для меня, потому что это не вызывают "визуальное мерцание".

чистое решение-установить rowcount = 0 и использовать свойство для heightconstraint вашей метки. Затем после того, как контент установлен вызов

CGSize sizeThatFitsLabel = [_subtitleLabel sizeThatFits:CGSizeMake(_subtitleLabel.frame.size.width, MAXFLOAT)];
_subtitleLabelHeightConstraint.constant = ceilf(sizeThatFitsLabel.height);

-(void) updateViewConstraints есть проблема с iOS 7.1.

в iOS 8 вы можете исправить проблемы с многострочным расположением меток в ячейке, вызвав cell.layoutIfNeeded() после удаления очереди и настройки ячейки. Вызов безвреден в iOS 9.

смотрите ответ Ника Снайдера. Это решение было взято из его кода на https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.swift.