How to scroll to the exact end of the UITableView?
Solution 1:
For Swift 3.0
Write a function :
func scrollToBottom(){
DispatchQueue.main.async {
let indexPath = IndexPath(row: self.dataArray.count-1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
and call it after you reload the tableview data
tableView.reloadData()
scrollToBottom()
Solution 2:
I would use more generic approach to this:
Swift4
extension UITableView {
func scrollToBottom(){
DispatchQueue.main.async {
let indexPath = IndexPath(
row: self.numberOfRows(inSection: self.numberOfSections-1) - 1,
section: self.numberOfSections - 1)
if hasRowAtIndexPath(indexPath) {
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
}
func scrollToTop() {
DispatchQueue.main.async {
let indexPath = IndexPath(row: 0, section: 0)
if hasRowAtIndexPath(indexPath) {
self.scrollToRow(at: indexPath, at: .top, animated: false)
}
}
}
func hasRowAtIndexPath(indexPath: IndexPath) -> Bool {
return indexPath.section < self.numberOfSections && indexPath.row < self.numberOfRows(inSection: indexPath.section)
}
}
Swift5
extension UITableView {
func scrollToBottom(isAnimated:Bool = true){
DispatchQueue.main.async {
let indexPath = IndexPath(
row: self.numberOfRows(inSection: self.numberOfSections-1) - 1,
section: self.numberOfSections - 1)
if self.hasRowAtIndexPath(indexPath: indexPath) {
self.scrollToRow(at: indexPath, at: .bottom, animated: isAnimated)
}
}
}
func scrollToTop(isAnimated:Bool = true) {
DispatchQueue.main.async {
let indexPath = IndexPath(row: 0, section: 0)
if self.hasRowAtIndexPath(indexPath: indexPath) {
self.scrollToRow(at: indexPath, at: .top, animated: isAnimated)
}
}
}
func hasRowAtIndexPath(indexPath: IndexPath) -> Bool {
return indexPath.section < self.numberOfSections && indexPath.row < self.numberOfRows(inSection: indexPath.section)
}
}
Solution 3:
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)
}
}