AutoLayout multiline UILabel обрезает текст

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

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

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

скриншот iphone

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

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

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

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

Заглавие:ограничения заголовка

Деньги:денежные ограничения

Подзаголовок:ограничения субтитров

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

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

- (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: а также на основе ответов на другие вопросы. Но независимо от того, что это просто не макет.

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

4 ответа

Решение

Нашел еще лучший ответ после прочтения этого: 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 всегда правильно.

Обновить:

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

В 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
    }
}

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

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

- (void) layoutSubviews {
    [super layoutSubviews];

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

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

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

Я потратил некоторое время на обертывание камеры, выглядя так:

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

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

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

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

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

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

Чтобы избежать ошибки 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's 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;
    }
}

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

titleLabel.adjustsFontSizeToFitWidth = true

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

Другие вопросы по тегам