AutoLayout многострочный UILabel, отрезающий некоторый текст

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

  • Заголовок, titleLbl - синяя рамка в изображении
  • Деньги, moneyLbl - зеленый квадрат на картинке
  • Субтитры/Описание, subTitleLbl - красная рамка в изображении

Пока у меня есть это:

iphone screenshot

Чего я хочу достичь:

  • Зеленый ящик должен всегда отображаться на 1 линии полностью
  • Значение внутри зеленого окна должно быть центрировано в соответствии с синим полем
  • Синий квадрат займет оставшееся пространство (-8px для горизонтального ограничения между 2) и отображает на нескольких строках, если необходимо
  • Красное поле должно быть внизу и отображать как можно больше строк.

Как вы видите, это почти все работает, за исключением примера в последней строке. Я попытался исследовать это и из того, что я могу собрать, это как-то связано с внутренним размером содержимого. Многие сообщения в Интернете рекомендуют установку setPreferredMaxLayoutWidth, я сделал это в нескольких местах для titleLbl, и это не повлияло. Только когда hardcoding на 180 я получил результаты, но также добавил пробелы/дополнения к другим синим полям вверху/под текстом, значительно увеличивая пространство между красными/синими квадратами.

Вот мои ограничения:

Название: title constraints

Деньги: money constraints

Субтитры: subtitle constraints

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

Я создал ivar ячейки и использовал его для создания высоты ячейки:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    [sizingCellTitleSubMoney.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
    [sizingCellTitleSubMoney.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
    [sizingCellTitleSubMoney.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];

    [sizingCellTitleSubMoney layoutIfNeeded];

    CGSize size = [sizingCellTitleSubMoney.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    return size.height+1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MATitleSubtitleMoneyTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifierTitleSubTitleMoney];

    if(!cell)
    {
        cell = [[MATitleSubtitleMoneyTableViewCell alloc] init];
    }


    cell.titleLbl.layer.borderColor = [UIColor blueColor].CGColor;
    cell.titleLbl.layer.borderWidth = 1;

    cell.subtitleLbl.layer.borderColor = [UIColor redColor].CGColor;
    cell.subtitleLbl.layer.borderWidth = 1;

    cell.moneyLbl.layer.borderColor = [UIColor greenColor].CGColor;
    cell.moneyLbl.layer.borderWidth = 1;


    [cell.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
    [cell.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
    [cell.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];

    return cell;
}

Я пробовал настройку setPreferredMaxLayoutWidth внутри подклассов ячейки:

- (void)layoutSubviews
{
    [super layoutSubviews];

    NSLog(@"Money lbl: %@", NSStringFromCGRect(self.moneyLbl.frame));
    NSLog(@"prefferredMaxSize: %f \n\n", self.moneyLbl.frame.origin.x-8);

    [self.titleLbl setPreferredMaxLayoutWidth: self.moneyLbl.frame.origin.x-8];
}

Журнал:

2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] Money lbl: {{209, 20}, {91, 61}}
2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] prefferredMaxSize: 201.000000 

Это то, что я ожидал бы, так как 8px между двумя элементами и консоль проверяет это. Он отлично работает в первой строке, независимо от того, сколько текста я добавляю в синюю рамку, что совпадает с раскадрой, но любое увеличение до зеленой коробки отменяет вычисление ширины. Я попытался установить это в tableView:willDisplayCell и tableView:heightForRowAtIndexPath:, а также на основе ответов других вопросов. Но независимо от того, что это просто не макет.

Любая помощь, идеи или комментарии были бы с благодарностью

Ответы

Ответ 1

Нашел еще лучший ответ после прочтения этого: http://johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights

создание подкласса UILabel для переопределения layoutSubviews следующим образом:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);

    [super layoutSubviews];
}

Это гарантирует, что preferredMaxLayoutWidth всегда корректен.

Update:

После нескольких выпусков iOS и тестирования большего количества пользователей, я обнаружил, что вышеуказанное не было достаточным для каждого случая. Начиная с iOS 8 (я считаю), при некоторых обстоятельствах только вызов didSet bounds вызывался вовремя до загрузки экрана.

В iOS 9 совсем недавно я столкнулся с другой проблемой при использовании модального UIVIewController с UITableView, который как layoutSubviews, так и set bounds назывался после heightForRowAtIndexPath. После большой отладки единственным решением было переопределить set frame.

Теперь приведенный ниже код необходим для обеспечения того, чтобы он работал во всех iOS, контроллерах, размерах экрана и т.д.

override public func layoutSubviews()
{
    super.layoutSubviews()
    self.preferredMaxLayoutWidth = self.bounds.width
    super.layoutSubviews()
}

override public var bounds: CGRect
{
    didSet
    {
        self.preferredMaxLayoutWidth = self.bounds.width
    }
}

override public var frame: CGRect
{
    didSet
    {
        self.preferredMaxLayoutWidth = self.frame.width
    }
}

Ответ 2

Я не уверен, что понимаю вас правильно, и мое решение действительно далеки от лучшего, но вот оно:

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

- (void) layoutSubviews {
    [super layoutSubviews];

    [self.badLabel setNeedsLayout];
    [self.badLabel layoutIfNeeded];

    self.badLabelHeightConstraint.constant = self.badLabel.intrinsicContentSize.height;
}

и альта! Пожалуйста, попробуйте этот метод и скажите мне результаты, спасибо.

Ответ 3

Omg, у меня была такая же проблема. Ответ @Simon McLoughlin мне не помог. Но

titleLabel.adjustsFontSizeToFitWidth = true

сделал волшебство! Это работает, я не знаю, почему, но это так. И да, ваша метка должна иметь явное значение preferredMaxLayoutWidth.

Ответ 4

Я потратил некоторое время на завершение ячейки, выглядя так:

+--------------------------------------------------+
| This is my cell.contentView                      |
| +------+ +-----------------+ +--------+ +------+ |
| |      | |    multiline    | | title2 | |      | |
| | img  | +-----------------+ +--------+ | img  | |
| |      | +-----------------+ +--------+ |      | |
| |      | |     title3      | | title4 | |      | |
| +------+ +-----------------+ +--------+ +------+ |
|                                                  |
+--------------------------------------------------+

После многих попыток я пришел с решением, как использовать прототип, который действительно работает.

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

Прежде всего, наша ячейка 'prototype' должна содержать только макет, если мы не находимся в "реальной" среде.

Поэтому я создал флаг 'heightCalculationInProgress'.

Когда кто-то (tableView dataSource) запрашивает вычисление высоты, мы предоставляем данные прототипу ячейки с использованием блока: И затем мы просим, ​​чтобы наша прототипная ячейка смоделировала контент и захватила его высоту. Простой.

Во избежание ошибки iOS7 с рекурсией layoutSubview есть переменная layoutingLock

- (CGFloat)heightWithSetupBlock:(nonnull void (^)(__kindof UITableViewCell *_Nonnull cell))block {
    block(self);

    self.heightCalculationInProgress = YES;

    [self setNeedsLayout];
    [self layoutIfNeeded];

    self.layoutingLock = NO;
    self.heightCalculationInProgress = NO;

    CGFloat calculatedHeight = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    CGFloat height = roundf(MAX(calculatedHeight, [[self class] defaultHeight]));

    return height;
}

Этот "блок" извне может выглядеть так:

[prototypeCell heightWithSetupBlock:^(__kindof UITableViewCell * _Nonnull cell) {
    @strongify(self);
    cell.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 0);
    cell.dataObject = dataObject;
    cell.multilineLabel.text = @"Long text";
    // Include any data here
}];

И теперь начинается самая интересная часть. Оформление печатных изданий:

- (void)layoutSubviews {
    if(self.layoutingLock){
        return;
    }

    // We don't want to do this layouting things in 'real' cells
    if (self.heightCalculationInProgress) {

    // Because of:
    // Support for constraint-based layout (auto layout)
    // If nonzero, this is used when determining -intrinsicContentSize for multiline labels

    // We're actually resetting internal constraints here. And then we could reuse this cell with new data to layout it correctly
        _multilineLabel.preferredMaxLayoutWidth = 0.f;
        _title2Label.preferredMaxLayoutWidth = 0.f;
        _title3Label.preferredMaxLayoutWidth = 0.f;
        _title4Label.preferredMaxLayoutWidth = 0.f;
    }

    [super layoutSubviews];

    // We don't want to do this layouting things in 'real' cells
    if (self.heightCalculationInProgress) {

        // Make sure the contentView does a layout pass here so that its subviews have their frames set, which we
        // need to use to set the preferredMaxLayoutWidth below.
        [self.contentView setNeedsLayout];
        [self.contentView layoutIfNeeded];

        // Set the preferredMaxLayoutWidth of the mutli-line bodyLabel based on the evaluated width of the label frame,
        // as this will allow the text to wrap correctly, and as a result allow the label to take on the correct height.
        _multilineLabel.preferredMaxLayoutWidth = CGRectGetWidth(_multilineLabel.frame);
        _title2Label.preferredMaxLayoutWidth = CGRectGetWidth(_title2Label.frame);
        _title3Label.preferredMaxLayoutWidth = CGRectGetWidth(title3Label.frame);
        _title4Label.preferredMaxLayoutWidth = CGRectGetWidth(_title4Label.frame);
    }

    if (self.heightCalculationInProgress) {
        self.layoutingLock = YES;
    }
}