fbpx
How to add Load More / Infinite Scrolling in iOS using Swift

How to add Load More / Infinite Scrolling in iOS using Swift

In this tutorial, I’ll show you how to add the Load More/Infinite Scrolling feature into your TableView/CollectionView very easy by using the scrollViewDidScroll, in TableView, and the willDisplay in CollectionView.

TableView

We already have an UITableview filled with data, and we want to add the Load More/Infinite Scrolling feature.

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

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

That’s it!!

CollectionView

In this example we use the footer as Loading View using a ReusableView.

We already have a CollectionView filled with data.

First, we create an UICollectionReusableView and add an UIActivityIndicatorView on the center of that view.

Control + left click and drag the UIActivityIndicatorView into your .swift file and give the name activityIndicator.

and add ‘loadingresuableviewid‘ as Identifier.

Now, let’s go into our CollectionViewController where we have the UICollectionView, and register the Loading ReusableView in the viewDidLoad().

override func viewDidLoad() {
        super.viewDidLoad()
        //...
        
        //Register Loading Reuseable View
        let loadingReusableNib = UINib(nibName: "LoadingReusableView", bundle: nil)
        collectionView.register(loadingReusableNib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "loadingresuableviewid")

    }

In the UICollectionView‘s delegate and datasource, add the following method to return the size for the Loading View when it’s time to show it.

var isLoading = false

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        if self.isLoading {
            return CGSize.zero
        } else {
            return CGSize(width: collectionView.bounds.size.width, height: 55)
        }
    }

Set the reusable view in the CollectionView footer…

var loadingView: LoadingReusableView?

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        if kind == UICollectionView.elementKindSectionFooter {
            let aFooterView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "loadingresuableviewid", for: indexPath) as! LoadingReusableView
            loadingView = aFooterView
            loadingView?.backgroundColor = UIColor.clear
            return aFooterView
        }
        return UICollectionReusableView()
    }

..and we start and stop the activityIndicator‘s animation when the footer appears and disappears, respectively.

func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
        if elementKind == UICollectionView.elementKindSectionFooter {
            self.loadingView?.activityIndicator.startAnimating()
        }
    }

    func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) {
        if elementKind == UICollectionView.elementKindSectionFooter {
            self.loadingView?.activityIndicator.stopAnimating()
        }
    }

Finally, we set the time we want more data to load while we scroll. In this example, it’ll load more data when the user sees the 10th cell from the bottom.

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if indexPath.row == itemsArray.count - 10 && !self.isLoading {
            loadMoreData()
        }
    }

    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.collectionView.reloadData()
                    self.isLoading = false
                }
            }
        }
    }

Done!!

You can find the final project here

If you have any questions feel free to DM me on Twitter @johncodeos or leave a comment below!

Subscribe
Notify of
guest
6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
SMA

Great Tutorial,

Would you have a suggestion on how to do this for a Chat/Messenger Usecase(infinit scroll to top?), I am struggling to get that working at the moment for collectionview with “WillDisplay”.

SMA

Hi John,

Thanks for answering.

if I invert the collectionview then the newest messages will be a the top and the oldest at the bottom… That is not how chat screen normally work, right?

The collection view content is per default scrolled to bottom to show the latest chat messages (default 100 messages are loaded) and when the user scroll up and reaches top(not the default tapping on navigation bar) the app should load 100 + x more older messages.

Abraham

Hi John, how can i stop loading when there’s no more data?