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

Last updated on: May 27, 2023

In this tutorial, I’ll show you how to add the Load More / Infinite Scrolling feature into your TableView/CollectionView very easily by using the willDisplay method, in TableView, and 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")
    }Code language: Swift (swift)

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
        }
    }Code language: Swift (swift)

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

func numberOfSections(in tableView: UITableView) -> Int {
    return 2
}Code language: Swift (swift)

In the cellForRowAt method we return 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: "loadingcellid", for: indexPath) as! LoadingCell
        cell.activityIndicator.startAnimating()
        return cell
    }
}Code language: Swift (swift)

…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
    }
}Code language: Swift (swift)

Next, we use the method willDisplay, to detect when the user is close to the end of the list to call the method…

var isLoading = false

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if indexPath.row == itemsArray.count - 10, !isLoading {
        loadMoreData()
    }
}Code language: Swift (swift)

loadMoreData() to download more data.

func loadMoreData() {
        if !self.isLoading {
            self.isLoading = true
            DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) { // Remove the 1-second delay if you want to load the data without waiting
                // Download more data here
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                    self.isLoading = false
                }
            }
        }
    }Code language: Swift (swift)

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")

    }Code language: Swift (swift)

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)
        }
    }Code language: Swift (swift)

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()
    }Code language: Swift (swift)

..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()
        }
    }Code language: Swift (swift)

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().asyncAfter(deadline: .now() + .seconds(1)) { // Remove the 1-second delay if you want to load the data without waiting
                // Download more data here
                DispatchQueue.main.async {
                    self.collectionView.reloadData()
                    self.isLoading = false
                }
            }
        }
    }Code language: Swift (swift)

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
8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments