UITableView динамические высоты ячеек только правильно после некоторой прокрутки



у меня есть UITableView с пользовательским UITableViewCell определяется в раскадровке с помощью автоматической компоновки. Ячейка имеет несколько многострочных UILabels.



The 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.

643   22  

22 ответов:

Я не знаю, это четко документировано или нет, но добавление [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;
}

добавлять [cell layoutIfNeeded] на cellForRowAtIndexPath не работает для ячеек, которые изначально прокручиваются вне поля зрения.

и не предваряет его с [cell setNeedsLayout].

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

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

это работало для меня, когда другие подобные решения не:

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    layoutIfNeeded()
}

это похоже на фактическую ошибку, так как я очень хорошо знаком с AutoLayout и как использовать UITableViewAutomaticDimension, однако я все еще иногда сталкиваюсь с этой проблемой. Я рад, что наконец-то нашел что-то, что работает как обходной путь.

у меня же опыт в одном из моих проектов.

почему это происходит?

ячейка, разработанная в раскадровке с некоторой шириной для некоторого устройства. Например 400 пикселей. Например, ваша метка имеет одинаковую ширину. Когда он загружается из раскадровки он имеет ширину 400px.

вот проблема:

tableView:heightForRowAtIndexPath: вызывается перед компоновкой ячейки это подвиды.

таким образом, он рассчитал высоту для метки и ячейки с шириной 400px. Но вы работаете на устройстве с экрана например, 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;
}

У меня есть аналогичная проблема, при первой загрузке высота строки не была рассчитана, но после некоторой прокрутки или перехода на другой экран, и я возвращаюсь к этому экрану, строки вычисляются. При первой загрузке мои элементы загружаются из интернета, а при второй загрузке мои элементы загружаются сначала из основных данных и перезагружаются из интернета, и я заметил, что высота строк вычисляется при перезагрузке из интернета. Поэтому я заметил, что когда 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 решить эту проблему

в моем случае последняя строка UILabel была усечена, когда ячейка отображалась в первый раз. Это произошло довольно случайно, и единственный способ правильно определить его размер-прокрутить ячейку из вида и вернуть ее обратно. Я пробовал все возможные решения, отображаемые до сих пор (layoutIfNeeded..reloadData), но ничего не сработало для меня. Хитрость заключалась в том, чтобы установить "Autoshrink" до Минимальный Масштаб Шрифта (0.5 для меня). Дайте ему попробовать

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

я попробовал большинство ответов на этот вопрос и не смог заставить ни один из них работать. Единственным функциональным решением, которое я нашел, было добавить следующее К моему UITableViewController подкласс:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    UIView.performWithoutAnimation {
        tableView.beginUpdates()
        tableView.endUpdates()
    }
}

The UIView.performWithoutAnimation вызов требуется, в противном случае вы увидите обычную анимацию представления таблицы при загрузке контроллера представления.

задание preferredMaxLayoutWidth помогает в моем случае. Я добавил

cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width

в моем коде.

Смотрите также однострочный текст занимает две строки в UILabel и http://openradar.appspot.com/17799811.

я попробовал все решения на этой странице, но сняв флажок Использовать классы размеров, а затем снова проверив его, решил мою проблему.

Edit: снятие флажка размер классов вызывает много проблем на раскадровке, поэтому я попробовал другое решение. Я заполнил tableView в представлении контроллера ViewDidLoad и ViewWillAppear методы. Это решило мою проблему.

Attatching screenshot for your referanceдля меня ни один из этих подходов не работал, но я обнаружил, что ярлык имел явное Preferred Width установить в Interface Builder. Удаление этого (снятие флажка "явный"), а затем с помощью UITableViewAutomaticDimension работал, как ожидалось.

У меня есть проблема с изменением размера метки, так что я не просто сделать
chatTextLabel.текст = chatMessage.сообщение 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)

  }
- (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;


}

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

в моем случае проблема с высотой ячейки возникает после загрузки начального представления таблицы, и происходит действие пользователя (нажатие на кнопку в ячейке, которая влияет на изменение высоты ячейки). Я не смог заставить ячейку изменить свою высоту, если я не сделаю:

[self.tableView reloadData];

Я пробовал

[cell layoutIfNeeded];

но это не сработало.

В Swift 3. Я должен был позвонить себе.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

в моем случае, я обновлял в другом цикле. Таким образом, высота 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
     // }
}

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

пришлось добавить следующее в 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() 

просто убедитесь, что вы не устанавливаете текст метки в методе делегата 'willdisplaycell' табличного представления. Установите текст метки в методе делегата 'cellForRowAtindexPath' для динамического вычисления высоты.

Добро пожаловать :)

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

я столкнулся с этой проблемой и исправил ее, переместив мой код инициализации представления/метки из tableView(willDisplay cell:) до tableView(cellForRowAt:).

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

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}

вызов cell.layoutIfNeeded() внутри cellForRowAt работал для меня на ios 10 и ios 11, но не на ios 9.

чтобы получить эту работу на iOS 9, также, я называю cell.layoutSubviews() и он сделал свое дело.

Comments

    Ничего не найдено.