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!
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”.
Yeah, you can do this by following these steps:
• Add Load More / Infinite Scrolling to your UITableView/UICollectionView
• Add the Scroll To Top feature.
• Make the CollectionView upside down using:
myChatCollectionView.transform = CGAffineTransform(scaleX: 1, y: -1)
• Because the cells will be upside down too, you need to reverse them again to look normal. Add
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
incellForItemAt
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.
By default, your chat collectionview needs to show the latest messages at the top, so when you invert the UICollectionView the latest messages will be at the bottom, and when the user scroll to the top will get the older messages.
You need to get the messages from your backend server in descending order, so you can get the most recent dates first.
Hi John, how can i stop loading when there’s no more data?
If you have 1000 images in your backend, and you show 20 images every time you scroll, then you have to check in every loadMoreData()to see if the current amount of items is smaller than the number of all items. When you there’s no more data, the footer view with the loading spinner will not appear.
thanks it’s really help me