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

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

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") }
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().async { // Fake background loading task for 2 seconds sleep(2) // 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
7 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?

AmrAngry

thanks it’s really help me