What you need is two things:
- Monitor current scroll offset and notify when the user is close to the bottom.
- When this threshold is triggered, signal the UICollectionView/UITableView that there are more rows, append new rows to your data source.
To do 1 is fairly trivial, we can simply extend UIScrollView:
extension UIScrollView {
/**
A convenience method that returns true when the scrollView is near to the bottom
- returns: true when the current contentOffset is close enough to the bottom to merit initiating a load for the next page
*/
func canStartLoadingNextPage() -> Bool {
//make sure that we have content and the scrollable area is at least larger than the scroll views bounds.
if contentOffset.y > 0 && contentSize.height > 0 && (contentOffset.y + CGRectGetHeight(bounds))/contentSize.height > 0.7
return true
}
return false
}
}
This function will return true when you reach 70% of the current content size but feel free to tweak as needed.
Now in our cellForRowAtIndexPath
we could technically call our function to determine whether we can append our dataset.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("niceReuseIdentifierName", forIndexPath: indexPath)
if collectionView.canStartLoadingNextPage() {
//now we can append our data
self.loadNextPage()
}
return cell
}
func loadNextPage() {
self.collectionView.performBatchUpdates({ () -> Void in
let nextBatch = self.pictures//or however you get the next batch
self.pictures.appendContentsOf(nextBatch)
self.collectionView.reloadSections(NSIndexSet(index: 0))
}, completion: nil)
}
And Voila, you should now have infinite scroll.
Improvements and Future Proofing
To improve this code you could have an object that can facilitate this preload for any UIScrollView subclass. This would also make it easier to transition over to networking calls and more complicated logic:
class ScrollViewPreloader {
enum View {
case TableView(tableView: UITableView)
case CollectionView(collectionView: UICollectionView)
}
private (set) var view: View
private (set) var pictures: [Picture] = []
init(view: View) {
self.view = view
}
func loadNextPageIfNeeded() {
func shouldLoadNextPage(scrollView: UIScrollView) -> Bool {
return scrollView.canStartLoadingNextPage()
}
switch self.view {
case .TableView(tableView: let tableView):
if shouldLoadNextPage(tableView) {
loadNextPageForTableView(tableView)
}
break
case .CollectionView(collectionView: let collectionView):
if shouldLoadNextPage(collectionView) {
loadNextPageForCollectionView(collectionView)
}
break
}
}
private func loadNextBatchOfPictures() -> [Picture] {
let nextBatch = self.pictures//or however you get the next batch
self.pictures.appendContentsOf(nextBatch)
return self.pictures
}
private func loadNextPageForTableView(tableView: UITableView) {
tableView.beginUpdates()
loadNextBatchOfPictures()
tableView.reloadData()//or you could call insert functions etc
tableView.endUpdates()
}
private func loadNextPageForCollectionView(collectionView: UICollectionView) {
collectionView.performBatchUpdates({ () -> Void in
self.loadNextBatchOfPictures()
collectionView.reloadSections(NSIndexSet(index: 0))
}, completion: nil)
}
}
struct Picture {
}
and you would be able to call it using the loadNextPageIfNeeded()
function inside cellForRow