it-swarm.com.ru

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

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

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

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

iphone screenshot

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

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

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

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

Заглавие:title constraints

Деньги:money constraints

Субтитры: subtitle constraints

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

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

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

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

29
Simon McLoughlin

Нашел еще лучший ответ после прочтения этого: 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 назывались afterheightForRowAtIndexPath. После большой отладки единственным решением было переопределить 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
    }
}
27
Simon McLoughlin

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

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

- (void) layoutSubviews {
    [super layoutSubviews];

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

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

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

2
Nikita Took

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

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

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

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

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

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

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

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

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

titleLabel.adjustsFontSizeToFitWidth = true

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

0
Mike Demidov