Summary My UITableView with dynamic cell heights randomly scrolls when inserting new cells at the top.
Detailed Explanation As part of the UITableViewController, to avoid inaccurate scrolling when I trigger reloadData() and scroll I have the following working implementation based on this stack overflow answer:
var cellHeightsDictionary: [IndexPath: CGFloat] = [:]
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeightsDictionary[indexPath] = cell.frame.size.height
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = cellHeightsDictionary[indexPath] {
return height
}
return UITableView.automaticDimension
}
This works very well except when adding new data at the top in the data source and triggering reloadData(). When doing that, I've not found a proper implementation that prevents the table from randomly scrolling... This is the problem I'm trying to solve.
What I've tried
I tried using the difference between contentSize before and after the update, and then updating the scrolling position. Unfortunately, it seems -based on what I've read in the Apple docs- that when using Dynamic cells the contentSize can't be trusted for this purpose and was not working for me. Nontheless for reference, here's one of my implementations.
UIView.performWithoutAnimation { //Detect exact position of the cell in relation to navBar let navBar = navigationController?.navigationBar let cellRectInTable = tableView.rectForRow(at: IndexPath(row: topArticle + countOfAddedItems, section: 0)) let positionVsNavBar = tableView.convert(cellRectInTable.origin, to: navBar) let positionVsTop = tableView.convert(cellRectInTable.origin, to: tableView.superview) let offsetOfCell = positionVsTop.y - positionVsNavBar.y tableView.reloadData() tableView.scrollToRow(at: IndexPath(row: topArticle + countOfAddedItems, section: 0), at: .top, animated: false) tableView.contentOffset.y = tableView.contentOffset.y + offsetOfCell }
I tried setting tableView.estimatedRowHeight = 0, tableView.estimatedSectionHeaderHeight = 0 and tableView.estimatedSectionFooterHeight = 0 before the addition, but it seemed to have no impact whatsoever.
The approach that seemed to work best was when I was getting the heights of cells from the cellHeightsDictionary and sum them based on the number of additions. This worked sometimes flawlessly but sometimes not. Also, when adding a lot of rows it may crash as it was not finding a value in cellHeightsDictionary. I tried multiple variations of this but did not get it to work. Equally important, I'm not sure exactly why this gave me best results so I may be missing something obvious. I have a feeling somewhere in this last implementation is the answer but I've been unable to find it yet.
var iteratorCounter = 0
var heightCum = CGFloat(integerLiteral: 0)
while iteratorCounter < countOfAddedItems {
heightCum = heightCum + cellHeightsDictionary[IndexPath(row: iteratorCounter, section: 0)]!
iteratorCounter = iteratorCounter + 1
}UIView.performWithoutAnimation { tableView.reloadData() tableView.layoutIfNeeded() tableView.contentOffset.y = tableView.contentOffset.y + heightCum }
I've tried everything I can think of and searched everywhere I thought of with no luck. Any help appreciated
Thanks a million!