Динамические высоты ячейки UITableView корректны только после прокрутки
У меня есть UITableView
с пользовательским UITableViewCell
, определенным в раскадровке, с использованием автоматического макета. Ячейка имеет несколько многострочных UILabels
.
UITableView
, по-видимому, правильно вычисляет высоты ячеек, но для первых нескольких ячеек высота не распределяется должным образом между метками.
После прокрутки немного работает все, как ожидалось (даже из исходных ячеек).
- (void)viewDidLoad {
[super viewDidLoad]
// ...
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
// ...
// Set label.text for variable length string.
return cell;
}
Есть ли что-то, что я могу упустить, что приводит к тому, что автомаркет не сможет выполнить свою работу в первые несколько раз?
Я создал образец проекта, который демонстрирует это поведение.
![Sample project: Top of table view from sample project on first load.]()
![Sample project: Same cells after scrolling down and back up.]()
Ответы
Ответ 1
Я не знаю, что это четко документировано или нет, но добавление [cell layoutIfNeeded]
перед возвратом ячейки решает вашу проблему.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
NSUInteger n1 = firstLabelWordCount[indexPath.row];
NSUInteger n2 = secondLabelWordCount[indexPath.row];
[cell setNumberOfWordsForFirstLabel:n1 secondLabel:n2];
[cell layoutIfNeeded]; // <- added
return cell;
}
Ответ 2
Это работало для меня, когда другие подобные решения не выполнялись:
override func didMoveToSuperview() {
super.didMoveToSuperview()
layoutIfNeeded()
}
Это похоже на реальную ошибку, так как я очень хорошо знаком с AutoLayout и как использовать UITableViewAutomaticDimension, однако я все же иногда сталкиваюсь с этой проблемой. Я рад, что наконец нашел что-то, что работает в качестве обходного пути.
Ответ 3
Добавление [cell layoutIfNeeded]
в cellForRowAtIndexPath
не работает для ячеек, которые изначально прокручиваются вне поля зрения.
Он не предваряет его [cell setNeedsLayout]
.
Вам все равно придется прокручивать определенные ячейки и вернуться в поле зрения, чтобы они правильно изменяли размер.
Это довольно неприятно, так как большинство разработчиков имеют динамический тип, AutoLayout и Self-Sizing Cells, которые работают нормально - за исключением этого неприятного случая. Эта ошибка влияет на все мои "более высокие" контроллеры табличных представлений.
Ответ 4
У меня был такой же опыт в одном из моих проектов.
Почему это происходит?
Ячейка, разработанная в Раскадке с некоторой шириной для некоторого устройства. Например, 400 пикселей. Например, ваш ярлык имеет такую же ширину. Когда он загружается из раскадровки, он имеет ширину 400 пикселей.
Вот проблема:
tableView:heightForRowAtIndexPath:
, вызванный до расположения ячеек, который он просматривает.
Итак, он рассчитал высоту для метки и ячейки с шириной 400 пикселей. Но вы запускаете устройство с экраном, например, 320 пикселей. И эта автоматически рассчитанная высота неверна. Просто потому, что ячейка layoutSubviews
происходит только после tableView:heightForRowAtIndexPath:
Даже если вы установите preferredMaxLayoutWidth
для своей метки вручную в layoutSubviews
, это не поможет.
Мое решение:
1) Подкласс UITableView
и переопределить dequeueReusableCellWithIdentifier:forIndexPath:
. Установите ширину ячейки равной ширине таблицы и силовой ячейке.
- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
CGRect cellFrame = cell.frame;
cellFrame.size.width = self.frame.size.width;
cell.frame = cellFrame;
[cell layoutIfNeeded];
return cell;
}
2) Подкласс UITableViewCell
.
Установите preferredMaxLayoutWidth
вручную для своих меток в layoutSubviews
. Также вам нужно вручную макет contentView
, потому что он автоматически не компоновщик после изменения рамки ячейки (я не знаю, почему, но это так)
- (void)layoutSubviews {
[super layoutSubviews];
[self.contentView layoutIfNeeded];
self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
Ответ 5
У меня есть аналогичная проблема, при первом загрузке высота строки не была рассчитана, но после некоторой прокрутки или перехода на другой экран, и я вернусь к этому экрану, строки вычисляются. При первой загрузке мои товары загружаются из Интернета, а при втором загрузке мои объекты загружаются сначала из Core Data и перезагружаются из Интернета, и я заметил, что высота строк вычисляется при перезагрузке из Интернета. Поэтому я заметил, что когда tableView.reloadData() вызывается во время анимации segue (такая же проблема с push и present segue), высота строки не вычислялась. Поэтому я скрыл tableview при инициализации представления и помещал загрузчик активности, чтобы предотвратить уродливый эффект для пользователя, и я вызываю tableView.reloadData через 300 мс, и теперь проблема решена. Я думаю, что это ошибка UIKit, но это обходное решение делает трюк.
Я поместил тезисы (Swift 3.0) в обработчик завершения загрузки элемента
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
self.tableView.isHidden = false
self.loader.stopAnimating()
self.tableView.reloadData()
})
Это объясняет, почему для некоторых людей установите reloadData в layoutSubviews, решив проблему
Ответ 6
В моем случае последняя строка UILabel была усечена, когда ячейка была отображена в первый раз. Это случалось довольно случайным образом, и единственный способ правильно определить его - прокрутить ячейку из представления и вернуть ее.
Я пробовал все возможные решения, отображаемые до сих пор (layoutIfNeeded..reloadData), но ничего не работало для меня. Трюк состоял в том, чтобы установить "Автосброс" в шкалу Minimuum Font Scale (0,5 для меня). Попробуйте
Ответ 7
Добавьте ограничение для всего содержимого в пользовательскую ячейку табличного представления, затем оцените высоту строки таблицы таблицы и установите высоту строки в автоматическое измерение с помощью нагрузки viewdid:
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 70
tableView.rowHeight = UITableViewAutomaticDimension
}
Чтобы исправить эту ошибку начальной загрузки, примените метод layoutIfNeeded с помощью ячейки пользовательского вида таблицы:
class CustomTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
self.layoutIfNeeded()
// Initialization code
}
}
Ответ 8
Я пробовал большинство ответов на этот вопрос и не мог заставить кого-либо из них работать. Единственное функциональное решение, которое я нашел, это добавить следующее в мой подкласс UITableViewController
:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIView.performWithoutAnimation {
tableView.beginUpdates()
tableView.endUpdates()
}
}
Требуется вызов UIView.performWithoutAnimation
, иначе вы увидите анимацию обычного представления таблицы, когда загружается контроллер вида.
Ответ 9
Настройка preferredMaxLayoutWidth
помогает в моем случае.
Я добавил
cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width
в моем коде.
Также ссылайтесь на Текст одной строки берет две строки в UILabel и http://openradar.appspot.com/17799811.
Ответ 10
Я перепробовал все решения на этой странице, но сняв флажок с использованием классов размера, а затем снова проверил его, решил мою проблему.
Изменение: Снятие отметки с классов размера вызывает много проблем на раскадровке, поэтому я попробовал другое решение. Я заполнил мое табличное представление в моих viewDidLoad
и viewWillAppear
контроллера viewWillAppear
. Это решило мою проблему.
Ответ 11
Для меня ни один из этих подходов не работал, но я обнаружил, что ярлык имеет явный Preferred Width
набор в Interface Builder. Удаление этого (снятие флажка "Явно" ), а затем с помощью UITableViewAutomaticDimension
работало, как ожидалось.
Ответ 12
cell.layoutIfNeeded()
внутри cellForRowAt
работал для меня на ios 10 и ios 11, но не на ios 9.
чтобы получить эту работу также на ios 9, я вызываю cell.layoutSubviews()
и он добился cell.layoutSubviews()
.
Ответ 13
У меня проблема с изменением размера ярлыка, поэтому я люблю только делать chatTextLabel.text = chatMessage.message chatTextLabel?.updateConstraints() после настройки текста
//полный код
func setContent() {
chatTextLabel.text = chatMessage.message
chatTextLabel?.updateConstraints()
let labelTextWidth = (chatTextLabel?.intrinsicContentSize().width) ?? 0
let labelTextHeight = chatTextLabel?.intrinsicContentSize().height
guard labelTextWidth < originWidth && labelTextHeight <= singleLineRowheight else {
trailingConstraint?.constant = trailingConstant
return
}
trailingConstraint?.constant = trailingConstant + (originWidth - labelTextWidth)
}
Ответ 14
В моем случае я обновлялся в другом цикле. Таким образом, высота tableViewCell была обновлена после установки labelText. Я удалил асинхронный блок.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:Identifier, for: indexPath)
// Check your cycle if update cycle is same or not
// DispatchQueue.main.async {
cell.label.text = nil
// }
}
Ответ 15
Просто убедитесь, что вы не устанавливаете текст ярлыка в методе делегата 'willdisplaycell' в представлении таблицы.
Установите текст метки в методе делегата 'cellForRowAtindexPath' для расчета динамической высоты.
Добро пожаловать:)
Ответ 16
Проблема заключается в том, что исходные ячейки загружаются до того, как мы получим допустимую высоту строки.
Обходной путь заключается в том, чтобы заставить таблицу перезагрузить, когда появится представление.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.tableView reloadData];
}
Ответ 17
ни одно из вышеперечисленных решений не сработало для меня, вот этот магический рецепт сработал: вызывайте их в следующем порядке:
tableView.reloadData()
tableView.layoutIfNeeded() tableView.beginUpdates() tableView.endUpdates()
мои данные tableView заполняются из веб-службы, при обратном вызове соединения я пишу вышеупомянутые строки.
Ответ 18
В моем случае проблема с высотой ячейки происходит после загрузки начального представления таблицы и происходит действие пользователя (нажатие на кнопку в ячейке, которая влияет на изменение высоты ячейки). Мне не удалось заставить ячейку изменить ее высоту, если я не сделаю:
[self.tableView reloadData];
Я попытался
[cell layoutIfNeeded];
но это не сработало.
Ответ 19
В Swift 3. Мне приходилось вызывать self.layoutIfNeeded() каждый раз, когда я обновляю текст повторно используемой ячейки.
import UIKit
import SnapKit
class CommentTableViewCell: UITableViewCell {
static let reuseIdentifier = "CommentTableViewCell"
var comment: Comment! {
didSet {
textLbl.attributedText = comment.attributedTextToDisplay()
self.layoutIfNeeded() //This is a fix to make propper automatic dimentions (height).
}
}
internal var textLbl = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
if textLbl.superview == nil {
textLbl.numberOfLines = 0
textLbl.lineBreakMode = .byWordWrapping
self.contentView.addSubview(textLbl)
textLbl.snp.makeConstraints({ (make) in
make.left.equalTo(contentView.snp.left).inset(10)
make.right.equalTo(contentView.snp.right).inset(10)
make.top.equalTo(contentView.snp.top).inset(10)
make.bottom.equalTo(contentView.snp.bottom).inset(10)
})
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let comment = comments[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CommentTableViewCell.reuseIdentifier, for: indexPath) as! CommentTableViewCell
cell.selectionStyle = .none
cell.comment = comment
return cell
}
commentsTableView.rowHeight = UITableViewAutomaticDimension
commentsTableView.estimatedRowHeight = 140
Ответ 20
Ни одно из вышеперечисленных решений не было выполнено, но была использована следующая комбинация предложений.
Придется добавить в viewDidLoad() следующее.
DispatchQueue.main.async {
self.tableView.reloadData()
self.tableView.setNeedsLayout()
self.tableView.layoutIfNeeded()
self.tableView.reloadData()
}
Вышеупомянутая комбинация reloadData, setNeedsLayout и layoutIfNeeded работала, но не какая-либо другая. Однако это может быть специфично для ячеек в проекте. И да, пришлось дважды вызвать reloadData, чтобы заставить его работать.
Также установите в viewDidLoad следующее
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight
В tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
cell.setNeedsLayout()
cell.layoutIfNeeded()
Ответ 21
В моем случае представление стека в ячейке вызывало проблему. Похоже, это ошибка. Как только я удалил его, проблема была решена.
Ответ 22
Для тех, кто все еще сталкивается с этой проблемой, попробуйте эту волшебную линию
tableview.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
Просто убедитесь, что вы вызываете это после заполнения таблицы данными, чтобы быть уверенным, что раздел 0 и строка 0 всегда доступны, в противном случае происходит сбой, но сначала вы можете проверить их доступность, надеюсь, это поможет.
Ответ 23
Только для iOS 12+, начиная с 2019 года...
Непрерывный пример случайной некомпетентности Apple, когда проблемы продолжаются буквально годы.
Кажется, дело в том, что
cell.layoutIfNeeded()
return cell
это исправит. (Вы теряете некоторое представление, конечно.)
Такова жизнь с Apple.
Ответ 24
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
// call the method dynamiclabelHeightForText
}
используйте описанный выше метод, который динамически возвращает высоту строки.
И назначьте ту же самую динамическую высоту для используемой библиотеки.
-(int)dynamiclabelHeightForText:(NSString *)text :(int)width :(UIFont *)font
{
CGSize maximumLabelSize = CGSizeMake(width,2500);
CGSize expectedLabelSize = [text sizeWithFont:font
constrainedToSize:maximumLabelSize
lineBreakMode:NSLineBreakByWordWrapping];
return expectedLabelSize.height;
}
Этот код поможет вам найти динамическую высоту для отображения текста на этикетке.
Ответ 25
Я столкнулся с этой проблемой и исправил ее, переместив код инициализации моего представления/метки FROM tableView(willDisplay cell:)
TO tableView(cellForRowAt:)
.