EDIT : added a check on last item to prevent recursion calls. You'll have to implement the method defining whether the last item has been reached or not.
Better to use willDisplayCell method to check if which cell will be loaded.
Once we get the current indexPath.row is last we can load more cells.
This will load more cells on scrolling down.
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
// check if indexPath.row is last row
// Perform operation to load new Cell's.
}
I have implemented one solution that i found in stackoverflow, and it works fine, but i think the shinyuX's solution it's very easy to implement and works fine for my propose.
If someone wants a different solution can use this one below.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
// UITableView only moves in one direction, y axis
CGFloat currentOffset = scrollView.contentOffset.y;
CGFloat maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
//NSInteger result = maximumOffset - currentOffset;
// Change 10.0 to adjust the distance from bottom
if (maximumOffset - currentOffset <= 10.0) {
[self loadOneMorePage];
//[self methodThatAddsDataAndReloadsTableView];
}
}
Here is the Swift version of Pedro Romão's answer. When the user stops scrolling it checks if it has reached the bottom.
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// UITableView only moves in one direction, y axis
let currentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
// Change 10.0 to adjust the distance from bottom
if maximumOffset - currentOffset <= 10.0 {
self.loadMore()
}
}
Method 2: Reached last row
And here is the Swift version of shinyuX's answer. It checks if the user has reached the last row.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// set up cell
// ...
// Check if the last row number is the same as the last current data element
if indexPath.row == self.dataArray.count - 1 {
self.loadMore()
}
}
Example of a loadMore() method
I set up these three class variables for fetching batches of data.
// number of items to be fetched each time (i.e., database LIMIT)
let itemsPerBatch = 50
// Where to start fetching items (database OFFSET)
var offset = 0
// a flag for when all database items have already been loaded
var reachedEndOfItems = false
This is the function to load more items from the database into the table view.
func loadMore() {
// don't bother doing another db query if already have everything
guard !self.reachedEndOfItems else {
return
}
// query the db on a background thread
DispatchQueue.global(qos: .background).async {
// determine the range of data items to fetch
var thisBatchOfItems: [MyObjects]?
let start = self.offset
let end = self.offset + self.itemsPerBatch
// query the database
do {
// SQLite.swift wrapper
thisBatchOfItems = try MyDataHelper.findRange(start..<end)
} catch _ {
print("query failed")
}
// update UITableView with new batch of items on main thread after query finishes
DispatchQueue.main.async {
if let newItems = thisBatchOfItems {
// append the new items to the data source for the table view
self.myObjectArray.appendContentsOf(newItems)
// reload the table view
self.tableView.reloadData()
// check if this was the last of the data
if newItems.count < self.itemsPerBatch {
self.reachedEndOfItems = true
print("reached end of data. Batch count: \(newItems.count)")
}
// reset the offset for the next data query
self.offset += self.itemsPerBatch
}
}
}
}
class DocumentEventsTableViewController: UITableViewController, UITableViewDataSourcePrefetching {
var currentPage: Int = 1
let pageSize: Int = 10 // num of items in one page
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.prefetchDataSource = self
}
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
let upcomingRows = indexPaths.map { $0.row }
if let maxIndex = upcomingRows.max() {
let nextPage: Int = Int(ceil(Double(maxIndex) / Double(pageSize))) + 1
if nextPage > currentPage {
// Your function, which attempts to load respective page from the local database
loadLocalData(page: nextPage)
// Your function, which makes a network request to fetch the respective page of data from the network
startLoadingDataFromNetwork(page: nextPage)
currentPage = nextPage
}
}
}
}
For rather small pages (~ 10 items) you might want to manually add data for pages 1 and 2 because nextPage might be somewhere about 1-2 until the table has a few items to be scrolled well. But it will work great for all next pages.
Now, just add following line of code in UITableViewDelegate Method willDisplay Cell in your ViewController and make sure tableView.delegate = self
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// need to pass your indexpath then it showing your indicator at bottom
tableView.addLoading(indexPath) {
// add your code here
// append Your array and reload your tableview
tableView.stopLoading() // stop your indicator
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:ShowComplainCell = tableView.dequeueReusableCell(withIdentifier: "cell")! as! ShowComplainCell
let item = self.dataArray[indexPath.row] as! ComplainListItem;
let indexPathArray = NSArray(array: tableView.indexPathsForVisibleRows!)
let vIndexPath = indexPathArray.lastObject as! NSIndexPath
let lastItemReached = item.isEqual(self.dataArray.lastObject);
if (lastItemReached && vIndexPath.row == (self.dataArray.count - 1))
{
self.loadData()
}
return cell
}
indexPathArray: is visible rows.
vIndexPath:is visible last indexpath
load data
func loadData(){
if(isReloadTable){
let HUD = MBProgressHUD.showAdded(to: self.view, animated: true)
let manager :AFHTTPSessionManager = AFHTTPSessionManager()
var param = NSDictionary()
param = [
"category":cat_id,
"smart_user_id": USERDEF.value(forKey: "user_id") as! String,
"page":page,
"phone":phone! as String
] as [String : Any] as NSDictionary
print("param1 = \(param)")
manager.get("lists.php?", parameters: param, progress: nil, success: { (task:URLSessionDataTask, responseObject: Any) in
let adsArray = dic["results"] as! NSArray;
for item in adsArray {
let item = ComplainListItem(dictionary: item as! NSDictionary )
self.dataArray.add(item)
}
self.view.addSubview(self.cityTableView)
self.cityTableView.reloadData()
if(adsArray.count==10){
self.cityTableView.reloadData()
self.isReloadTable = true
self.page+=1
}else if(adsArray.count<10){
self.cityTableView.reloadData()
self.isReloadTable = false
}
HUD.hide(animated:true)
}) { (operation,error) -> Void in
print("error = \(error)")
HUD.hide(animated:true)
}
}
}
check your dataArray count which is myadsarray check to equal your data limit. then if dataArray count equal next page is called if not equal which is less then 10, all data is showed or finished.