AutoLayout multiline UILabel обрезает текст
Я пытаюсь выучить автоматическую разметку, наконец-то подумала, что уже поняла, когда это произошло. Я играю с прототипом клеток и меток. Я пытаюсь получить
- Заглавие,
titleLbl
- синяя коробка на картинке - Деньги,
moneyLbl
- зеленое поле на картинке - Подзаголовок / Описание,
subTitleLbl
- красная коробка на картинке
Пока у меня есть это:
Чего я хочу достичь:
- Зеленое поле всегда должно отображаться на 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 (я считаю), только при некоторых обстоятельствах 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
}
}
Я не уверен, что правильно вас понял, и мое решение действительно далеко от лучшего, но вот оно:
Я добавил ограничение высоты для вырезанной метки, связав это ограничение с выходом ячейки. Я переопределил метод 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.