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
1. 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 {
if debugMode == true{
print("updateTableViewAfterAddition: Adding and scrolling with no animation.")
}
//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
//Reload data
tableView.reloadData()
//Navigate to row
tableView.scrollToRow(at: IndexPath(row: topArticle + countOfAddedItems, section: 0), at: .top, animated: false)
//Add offset
tableView.contentOffset.y = tableView.contentOffset.y + offsetOfCell
}
2. I tried setting tableView.estimatedRowHeight = 0, tableView.estimatedSectionHeaderHeight = 0 and tableView.estimatedSectionFooterHeight = 0 before the addition, but it seemed to have no impact whatsoever.
3. 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,
Marc