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 function 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 function 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 function scrollViewDidScroll, from the embedded UIScrollView in the UITableView, to detect when the user is close to the end of the list to call the function…

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

That’s it!! 🥳😃

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!

Leave a Reply

avatar
  Subscribe  
Notify of

Stay Connected

Newsletter

Subcribe for weekly emails! Get my posts of the week right in your inbox!