swift ios add infinite scroll pagination to uitableview
Asked Answered
S

7

18

I wonder if tableview has any built-in function to add infinite scroll/pagination.

Right now my VC looks like this:

var data: JSON! = []

override func viewDidLoad() {
    super.viewDidLoad()

    //Init start height of cell
    self.tableView.estimatedRowHeight = 122
    self.tableView.rowHeight = UITableViewAutomaticDimension

    self.tableView.delegate = self
    self.tableView.dataSource = self

    savedLoader.startAnimation()
    //Load first page
    loadSaved(1)

}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return data.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCellWithIdentifier("aCell") as! SavedTableViewCell

    let info = data[indexPath.row]
    cell.configureWithData(info)

    return cell
}

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    performSegueWithIdentifier("WebSegue", sender: indexPath)

    tableView.deselectRowAtIndexPath(indexPath, animated: false)
}

I fetch my data using loadSaved(1) by giving the function the current page I want to load. The function makes a API request using alomofire then populate the var data: JSON! = [] with the data that should be displayed

So what I want to do is when I scroll to the bottom of the tableview loadSaved(2) should be called loading more data into the tableview

Selfeducated answered 29/8, 2016 at 20:57 Comment(2)
My answer here can help you #28246894Indefensible
github.com/canopas/MarqueeScrollJokester
T
29

Looks like amar’s answer might be a better solution now, but here’s my answer from 2017 anyway:

The UITableViewDelegate has a table​View(_:​will​Display:​for​Row​At:​) instance method which "tells the delegate the table view is about to draw a cell for a particular row."

In your case I would use it something like this:

override open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if indexPath.row == data.count-1 { //you might decide to load sooner than -1 I guess...
      //load more into data here
    }
}

Depending on your code, you may need some checks around this to ensure you don't end up in an infinite loop if you've loaded all your data...

Tayler answered 20/3, 2017 at 11:15 Comment(2)
I tried this method on swift 5 and it worked perfectly.Jospeh
@Jospeh did you try amar’s answer? https://mcmap.net/q/358009/-swift-ios-add-infinite-scroll-pagination-to-uitableviewTayler
I
7

No, the UITableView has not any built-in function to achieve the infinite scroll or load on-demand cells like you want. What you can use is the function scrollViewDidScroll(_:) in the UIScrollViewDelegate implemented by default in the UITableView and in this way know when the user scroll more than the original height defined in the UITableView.

For example like in this code:

var indexOfPageToRequest = 1

override func scrollViewDidScroll(scrollView: UIScrollView) {

    // calculates where the user is in the y-axis
    let offsetY = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height

    if offsetY > contentHeight - scrollView.frame.size.height {

        // increments the number of the page to request
        indexOfPageToRequest += 1

        // call your API for more data
        loadSaved(indexOfPageToRequest)

        // tell the table view to reload with the new data
        self.tableView.reloadData()
    }
}

To achieve the result of add the rest of the elements at the end of the UITableView you should add the new elements to the data source, in your case data inside your function loadSaved(numberOfPage).

I hope this help you.

Indefensible answered 31/8, 2016 at 23:41 Comment(0)
S
6

All the above answers are correct but for iOS 10 and above we have a very nice

func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])

This is a prefetch delegate which needs to be set

tableView.prefetchDataSource = self

RayWeinderlich has a nice tutorial on the topic. Since Rays is a dependable site i am not posting code here

Sagesagebrush answered 31/1, 2019 at 9:47 Comment(0)
T
4

I have modified Victor's answer and used it as ,

var indexOfPageRequest = 1
var loadingStatus = false

func loadData(){
    if !loadingStatus{
        loadingStatus = true
        viewModel.getData(pageIndex: indexOfPageRequest)
    }
}

override func scrollViewDidScroll(_ scrollView: UIScrollView) {

    // calculates where the user is in the y-axis
    let offsetY = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height

    if offsetY > contentHeight - scrollView.frame.size.height {

        // increments the number of the page to request
        indexOfPageRequest += 1

        // call your API for more data
        loadData()

        // tell the table view to reload with the new data
        self.tableView.reloadData()
    }
}

Reset loadingStatus to true when you receive data. Without checking if the view was already loading more data, the tableview was flickering.

Taciturn answered 5/5, 2017 at 5:57 Comment(2)
what if our tableview does not start from top, but from bottom? how can I make this kind of check at that case?Pursley
This way is more cpu consuming. It's better to check at which row is the user, then where he scrolled.Promontory
T
2

Ravi's answer looks good. But as he pointed out in the end, the tableView flickers a lot if you use scrollViewDidScroll(_ scrollView: UIScrollView)

This is because you are trying to reload tableView every time you are scrolling the tableView.

Instead you could use scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) delegate method to determine whether you have scrolled enough and have reached almost the end of the tableView.

override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height

        if offsetY > contentHeight - scrollView.frame.size.height {
               indexOfPageRequest += 1
               loadData()
               self.tableView.reloadData()
        }

    }
Thorin answered 16/2, 2019 at 11:24 Comment(0)
A
1

Above Ans are also right, but may be some one help out this code also.

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath){
    if(indexPath.row == self.arryOfData.count-1){
        if(self.pageNumber <= self.resPgNumber){
            if(remaining != 0){
                let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
                spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
                spinner.startAnimating()
                tableView.tableFooterView = spinner
                tableView.tableFooterView?.isHidden = false

                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    self.flgActivity = false
                    self.getActiveOrdersList()
                }
            }
            else{
                tableView.tableFooterView?.removeFromSuperview()
                let view = UIView()
                view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
                tableView.tableFooterView = view
                tableView.tableFooterView?.isHidden = true
            }
        }
        else{
            tableView.tableFooterView?.removeFromSuperview()
            let view = UIView()
            view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
            tableView.tableFooterView = view
            tableView.tableFooterView?.isHidden = true
        }
    }
    else{
        tableView.tableFooterView?.removeFromSuperview()
        let view = UIView()
        view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
        tableView.tableFooterView = view
        tableView.tableFooterView?.isHidden = true
    }
}
Amphibiotic answered 18/12, 2017 at 6:54 Comment(1)
I like the way you are checking the row that should be shown, but why you are adding hidden view on 3 places with the same code?Promontory
C
0

There are number of ways we can do this. The essense of all different ways, is to load next set of data when user scroll to last. I have implemented it via adding an extra special cell at the end of tableView and when that cell gets loaded in willDisplay cell: forRowAtIndexPath: which triggers next set of fetching of data.

Athough this is simple to implement but in larger apps at times we need to implement it many places. To avoid this, I wrote a small framework which is non-intrusive and can be easyly integrated.

Cover answered 5/1, 2020 at 6:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.