For an upcoming app I had to insert Dynamic Height cells while keeping the tableView offset -scrolling position- constant. This would typically be a trivial issue, but given that when using Dynamic Height cells nor the contentSize nor the offset can be trusted it’s a bit more tricky than it seems…
What I was trying to do was adding cells at the top of my tableView without any scroll taking place, similarly to what Twitter, Tweetbot or Twitterrific apps do.
Green cells in the “After” scenario show how the new cells have been inserted at the top, without affecting scrolling.
The approach that ended up working smoothly was:
Step 1. Storing in a variable the UITableViewCell below the Navigation Bar and storing the offset of that cell in relation to the Navigation Bar.
Step 2. Insert cells / reloadData.
Step 3.Scroll to the cell we saved before the insert/reload and then add the offset.
Here’s the code:
UIView.performWithoutAnimation {
//Step 1 Detect & Store
let topWillBeAt = getTopVisibleRow() + countOfAddedItems
let oldHeightDifferenceBetweenTopRowAndNavBar = heightDifferenceBetweenTopRowAndNavBar()
//Step 2 Insert
self.tableView.insertRows(at: arrayOfIndexPaths, with: .none)
//Step 3 Restore Scrolling
tableView.scrollToRow(at: IndexPath(row: topWillBeAt, section: 0), at: .top, animated: false)
tableView.contentOffset.y = tableView.contentOffset.y – oldHeightDifferenceBetweenTopRowAndNavBar
}
And supporting functions:
func getTopVisibleRow () -> Int {
//We need this to accounts for the translucency below the nav bar
let navBar = navigationController?.navigationBar
let whereIsNavBarInTableView = tableView.convert(navBar!.bounds, from: navBar)
let pointWhereNavBarEnds = CGPoint(x: 0, y: whereIsNavBarInTableView.origin.y + whereIsNavBarInTableView.size.height + 1)
let accurateIndexPath = tableView.indexPathForRow(at: pointWhereNavBarEnds)
return accurateIndexPath?.row ?? 0
}
func heightDifferenceBetweenTopRowAndNavBar()-> CGFloat{
let rectForTopRow = tableView.rectForRow(at:IndexPath(row: getTopVisibleRow(), section: 0))
let navBar = navigationController?.navigationBar
let whereIsNavBarInTableView = tableView.convert(navBar!.bounds, from: navBar)
let pointWhereNavBarEnds = CGPoint(x: 0, y: whereIsNavBarInTableView.origin.y + whereIsNavBarInTableView.size.height)
let differenceBetweenTopRowAndNavBar = rectForTopRow.origin.y – pointWhereNavBarEnds.y
return differenceBetweenTopRowAndNavBar
}
Questions / comments / suggestions? @MarcMasVi
PS. On another topic, if you get some jumpy scrolling with Dynamic Height cells I strongly suggest you to look into this question from Stack Overflow.
Marc