I tried Umair's approach, however in UITableView
s, sometimes there can be a section with 0 rows; in which case, the code points to an invalid index path (row 0 of an empty section is not a row).
Blindly minusing 1 from the number of rows/sections can be another pain point, as, again, the row/section could contain 0 elements.
Here's my solution to scrolling to the bottom-most cell, ensuring the index path is valid:
extension UITableView {
func scrollToBottomRow() {
DispatchQueue.main.async {
guard self.numberOfSections > 0 else { return }
// Make an attempt to use the bottom-most section with at least one row
var section = max(self.numberOfSections - 1, 0)
var row = max(self.numberOfRows(inSection: section) - 1, 0)
var indexPath = IndexPath(row: row, section: section)
// Ensure the index path is valid, otherwise use the section above (sections can
// contain 0 rows which leads to an invalid index path)
while !self.indexPathIsValid(indexPath) {
section = max(section - 1, 0)
row = max(self.numberOfRows(inSection: section) - 1, 0)
indexPath = IndexPath(row: row, section: section)
// If we're down to the last section, attempt to use the first row
if indexPath.section == 0 {
indexPath = IndexPath(row: 0, section: 0)
break
}
}
// In the case that [0, 0] is valid (perhaps no data source?), ensure we don't encounter an
// exception here
guard self.indexPathIsValid(indexPath) else { return }
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
func indexPathIsValid(_ indexPath: IndexPath) -> Bool {
let section = indexPath.section
let row = indexPath.row
return section < self.numberOfSections && row < self.numberOfRows(inSection: section)
}
}
reloadData
although seems to be synchronous, acts asynchronously. so you have to either enclose your code in adispatchAsync
block on main thread or find another way to do it. I have faced this issue many many times. scrolling to an specific index path is almost futile most of the time. you should use thesetContentOffset
method. – M. Porooshani