AutoLayout многострочный UILabel, отрезающий некоторый текст
Я пытаюсь изучить макет авто, наконец-то подумал, что получаю зависание, когда это произошло. Я играю с прототипами ячеек и ярлыков. Я пытаюсь иметь
- Заголовок,
titleLbl
- синяя рамка в изображении
- Деньги,
moneyLbl
- зеленый квадрат на картинке
- Субтитры/Описание,
subTitleLbl
- красная рамка в изображении
Пока у меня есть это:
Чего я хочу достичь:
- Зеленый ящик должен всегда отображаться на 1 линии полностью
- Значение внутри зеленого окна должно быть центрировано в соответствии с синим полем
- Синий квадрат займет оставшееся пространство (-8px для горизонтального ограничения между 2) и отображает на нескольких строках, если необходимо
- Красное поле должно быть внизу и отображать как можно больше строк.
Как вы видите, это почти все работает, за исключением примера в последней строке. Я попытался исследовать это и из того, что я могу собрать, это как-то связано с внутренним размером содержимого. Многие сообщения в Интернете рекомендуют установку setPreferredMaxLayoutWidth
, я сделал это в нескольких местах для titleLbl
, и это не повлияло. Только когда hardcoding на 180
я получил результаты, но также добавил пробелы/дополнения к другим синим полям вверху/под текстом, значительно увеличивая пространство между красными/синими квадратами.
Вот мои ограничения:
Название:
Деньги:
Субтитры:
Основываясь на тестах, которые я запускал с другими текстовыми образцами, он, похоже, использует ширину, как показано в раскадровке, но любая попытка исправить ее не работает.
Я создал 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;
}
}