We can paginate a UITableView
by implementing the tableView:willDisplayCell:forRowAtIndexPath
method. In it we can check if the displayed row index is nearing the last row in the model and if so, we fetch new data, append it to the model and reload the table view.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == self.dataList.count - self.dataOffset && self.shouldFetchMore && !self.isDataLoading {
UI.showLoadingForTableView(self.tableView) // Display loader at the bottom
self.api.fetchSomeData(offset: self.dataList.count, max: Const.dataFetchMaxLimit, success: { xs in
DispatchQueue.main.async {
UI.hideLoadingForTableView(self.tableView) // Hide loader
self.isDataLoading = false
self.dataList.append(contentsOf: xs)
self.reloadTableView()
if xs.count == 0 { self.shouldFetchMore = false } // If data returned is 0 => there are no more data to fetch
}
self.lastFetchedIndex += Constants.dataFetchMaxLimit // Increment the fetch index to include current offset
}, error: { _ in
self.shouldFetchMore = true
self.isDataLoading = false
DispatchQueue.main.async {
UI.hideLoadingForTableView(self.tableView)
self.reloadTableView()
}
})
}
}
Here, dataList
is the table's data model, dataOffset
is a constant, say 5
which means, if there are only five more cells to be displayed, paginate. The shouldFetchMore
variable indicates if there are more data available to paginate and isDataLoading
indicates if a fetch is already in progress.
We can show a loader activity view at the bottom of the table view to indicate that more data is being fetched.
class UI {
/// A loading indicator
private static var spinner: (UITableView) -> UIActivityIndicatorView = { tableView in
let s = UIActivityIndicatorView(style: .gray)
s.startAnimating()
s.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
return s
}
/// Show loading spinner at the bottom of the given table view
static func showLoadingForTableView(_ tableView: UITableView) {
tableView.tableFooterView = self.spinner(tableView)
tableView.tableFooterView?.isHidden = false
}
/// Hide loading spinner for the given table view if present
static func hideLoadingForTableView(_ tableView: UITableView) {
tableView.tableFooterView?.subviews.forEach({ view in
if view == self.spinner(tableView) {
view.removeFromSuperview()
}
})
tableView.tableFooterView?.isHidden = true
}
}