reloadData () из UITableView с динамическими высотами ячеек вызывает нервную прокрутку


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

в принципе, мой UITableView имеет динамические высоты ячеек для каждой ячейки. Если я не нахожусь в верхней части UITableView и я tableView.reloadData(), прокрутка становится нервным.

Я считаю, что это связано с тем, что, поскольку я перезагрузил данные, когда я прокручиваю вверх, UITableView пересчитывает высоту для каждой ячейки, входящей в видимость. Как мне смягчить это, или как я только reloadData от определенного IndexPath до конца UITableView?

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

9 63

9 ответов:

чтобы предотвратить прыжки, вы должны сохранить высоту ячеек при их загрузке и дать точное значение в tableView:estimatedHeightForRowAtIndexPath:

// declare cellHeightsDictionary
NSMutableDictionary *cellHeightsDictionary;

// initialize in code (thanks to @Gerharbo)
cellHeightsDictionary = @{}.mutableCopy;

// declare table dynamic row height and create correct constraints in cells
tableView.rowHeight = UITableViewAutomaticDimension;

// save height
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    [cellHeightsDictionary setObject:@(cell.frame.size.height) forKey:indexPath];
}

// give exact height value
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSNumber *height = [cellHeightsDictionary objectForKey:indexPath];
    if (height) return height.doubleValue;
    return UITableViewAutomaticDimension;
}

Swift 3 версия принятого ответа.

var cellHeights: [IndexPath : CGFloat] = [:]


func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cellHeights[indexPath] = cell.frame.size.height
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return cellHeights[indexPath] ?? 70.0 
}

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

func viewDidLoad() {
        super.viewDidLoad()
        tableView.estimatedRowHeight = 100//close to your cell height
    }

Если у вас есть верность ячеек в разных разделах, то я думаю, что лучшее место

    func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//Return different sizes for different cells if you need to
            return 100
        }

@Igor ответ работает нормально в этом случае, Swift-4 код.

// declaration & initialization  
var cellHeightsDictionary: [IndexPath: CGFloat] = [:]  

в следующих методах UITableViewDelegate

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  // print("Cell height: \(cell.frame.size.height)")
  self.cellHeightsDictionary[indexPath] = cell.frame.size.height
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
  if let height =  self.cellHeightsDictionary[indexPath] {
    return height
  }
  return UITableViewAutomaticDimension
}

я столкнулся с этим сегодня и заметил:

  1. это только iOS 8, действительно.
  2. переменная cellForRowAtIndexPath не помогает.

исправление было на самом деле довольно просто:

переопределить estimatedHeightForRowAtIndexPath и убедитесь, что она возвращает правильные значения.

С этим все странное дрожание и прыжки в моих UITableViews прекратились.

примечание: Я на самом деле знаю размер моих клеток. Есть только два возможных значения. Если ваш ячейки действительно имеют переменный размер, тогда вы можете кэшировать cell.bounds.size.height С tableView:willDisplayCell:forRowAtIndexPath:

вы можете на самом деле перезагрузить только определенные строки с помощью reloadRowsAtIndexPaths, например:

tableView.reloadRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None)

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

tableView.beginUpdates()
tableView.endUpdates()

Я пробовал все обходные пути выше, но ничего не сработало.

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

Swift 4

let lastContentOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastContentOffset, animated: false)

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

extension UITableView {

    func reloadWithoutAnimation() {
        let lastScrollOffset = contentOffset
        beginUpdates()
        endUpdates()
        layer.removeAllAnimations()
        setContentOffset(lastScrollOffset, animated: false)
    }
}

наконец-то ..

tableView.reloadWithoutAnimation()

попробуйте позвонить cell.layoutSubviews() перед возвратом ячейки в func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?. Это известная ошибка в iOS8.

вот немного более короткая версия:

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return self.cellHeightsDictionary[indexPath] ?? UITableViewAutomaticDimension
}