17

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

Bista
  • 7,689
  • 3
  • 24
  • 52
user2636197
  • 3,432
  • 6
  • 40
  • 64

8 Answers8

27

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...

lukkea
  • 3,526
  • 2
  • 35
  • 49
6

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.

Victor Sigler
  • 22,039
  • 12
  • 83
  • 98
3

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.

Ravi_sankar
  • 317
  • 2
  • 11
3

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

amar
  • 4,147
  • 6
  • 35
  • 48
3

First, create an UITableViewCell with a Xib file and add an UIActivityIndicatorView on the center of the cell.

Then, Control+left click and drag the UIActivityIndicatorView into your .swift file and give the name activityIndicator…… and add ‘loadingcellid‘ as Identifier.

Now, let’s go into our TableViewController where we have the UITableView, and register the Loading Cell in the viewDidLoad().

override func viewDidLoad() {
    super.viewDidLoad()
    //...
    
    //Register Loading Cell
    let tableViewLoadingCellNib = UINib(nibName: "LoadingCell", bundle: nil)
    self.tableView.register(tableViewLoadingCellNib, forCellReuseIdentifier: "tableviewloadingcellid")
}

In the UITableView’s delegate and datasource we add in the method numberOfRowsInSection the following code.

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 0 {
        //Return the amount of items
        return itemsArray.count
    } else if section == 1 {
        //Return the Loading cell
        return 1
    } else {
        //Return nothing
        return 0
    }
}

..and we return 2 sections(one for the items, and one for the Loading Cell).

  func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }

In the cellForRowAt method we returning the cell for each section:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.section == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "tableviewitemcellid", for: indexPath) as! TableViewItemCell
            cell.itemLabel.text = itemsArray[indexPath.row]
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: 
       "tableviewloadingcellid", for: indexPath) as! TableViewLoadingCell
            cell.activityIndicator.startAnimating()
            return cell
        }
    }

…and also we set the row height.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.section == 0 {
        return 44 //Item Cell height
    } else {
        return 55 //Loading Cell height
    }
}

Next, we use the method scrollViewDidScroll, from the embedded UIScrollView in the UITableView, to detect when the user is close to the end of the list to call the method…

 var isLoading = false

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height

        if (offsetY > contentHeight - scrollView.frame.height * 4) && !isLoading {
            loadMoreData()
        }
    }

… loadMoreData() to download more data.

func loadMoreData() {
    if !self.isLoading {
        self.isLoading = true
        DispatchQueue.global().async {
            // Fake background loading task for 2 seconds
            sleep(2)
            // Download more data here
            DispatchQueue.main.async {
                self.tableView.reloadData()
                self.isLoading = false
            }
        }
    }
}

It's working well, Form more:- https://johncodeos.com/how-to-add-load-more-infinite-scrolling-in-ios-using-swift/

enter image description here

Paresh Mangukiya
  • 14,668
  • 7
  • 90
  • 90
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()
        }

    }
iPhoneDeveloper
  • 768
  • 1
  • 10
  • 22
0

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
    }
}
Vishal Wagh
  • 101
  • 10
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.

Kraming
  • 91
  • 1
  • 5