Динамические высоты ячейки 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, иначе вы увидите анимацию обычного представления таблицы, когда загружается контроллер вида.

Ответ 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:).