I have a UITableView
that is populated with cells with dynamic height. I would like the table to scroll to the bottom when the view controller is pushed from vi
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)
}
}
To scroll to the end of your TableView you can use the following function, which also works for ScrollViews.
It also calculates the safe area on the bottom for iPhone X and newer. The call is made from the main queue, to calculate the height correctly.
func scrollToBottom(animated: Bool) {
DispatchQueue.main.async {
let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.size.height + self.safeAreaBottom)
if bottomOffset.y > 0 {
self.setContentOffset(bottomOffset, animated: animated)
}
}
}
A little update of @Umair answer in case your tableView is empty
func scrollToBottom(animated: Bool = true, delay: Double = 0.0) {
let numberOfRows = tableView.numberOfRows(inSection: tableView.numberOfSections - 1) - 1
guard numberOfRows > 0 else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [unowned self] in
let indexPath = IndexPath(
row: numberOfRows,
section: self.tableView.numberOfSections - 1)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: animated)
}
}
If you used UINavigationBar
with some height and UITableView
in the UIView
try it to subtract UINavigationBar
height from UITableView
's frame height . Cause your UITableView
's top
point same with UINavigationBar
's bottom
point so this affect your UITableView
's bottom
items to scrolling.
Swift 3
simpleTableView.frame = CGRect.init(x: 0, y: navigationBarHeight, width: Int(view.frame.width), height: Int(view.frame.height)-navigationBarHeight)