I\'m trying to paginate data (infinitely scroll my tableview) using firestore. I\'ve integrated the code google gives for pagination as best I can, but I\'m still having problem
Simple, Fast and easy way is...
class FeedViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, FeedCellDelegate {
private var quotes = [Quote]() {
    
    didSet{ tbl_Feed.reloadData() }
}
var quote: Quote?
var fetchCount = 10
@IBOutlet weak var tbl_Feed: UITableView!
override func viewDidLoad() {
    super.viewDidLoad()
    fetchPost() 
}
// MARK: - API
func fetchPost() {
    
    reference(.Quotes).limit(to: getResultCount).getDocuments { (snapshot, error) in
        
        guard let documents = snapshot?.documents else { return }
        
        documents.forEach { (doc) in
            
            let quotes = documents.map {(Quote(dictionary: $0.data()))}
            
            self.quotes = quotes
        }
    }
}  
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    
    let currentOffset = scrollView.contentOffset.y
    let maxxOffset = scrollView.contentSize.height - scrollView.frame.size.height
    
    if maxxOffset - currentOffset <= 300 { // Your cell size 300 is example
        fetchCount += 5
        fetchPost()
        print("DEBUG: Fetching new Data")
    }
}
}
A little late in the game, but I would like to share how I do it, using the query.start(afterDocument:) method.
class PostsController: UITableViewController {
    let db = Firestore.firestore()
    var query: Query!
    var documents = [QueryDocumentSnapshot]()
    var postArray = [Post]()
    override func viewDidLoad() {
        super.viewDidLoad()
        query = db.collection("myCollection")
                  .order(by: "post", descending: false)
                  .limit(to: 15)
        getData()
    }
    func getData() {
        query.getDocuments() { (querySnapshot, err) in
            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                querySnapshot!.documents.forEach({ (document) in
                    let data = document.data() as [String: AnyObject]
                    //Setup your data model
                    let postItem = Post(post: post, id: id)
                    self.postArray += [postItem]
                    self.documents += [document]
                })
                self.tableView.reloadData()
            }
        } 
    }
    func paginate() {
        //This line is the main pagination code.
        //Firestore allows you to fetch document from the last queryDocument
        query = query.start(afterDocument: documents.last!)
        getData()
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return postArray.count
    }
    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        // Trigger pagination when scrolled to last cell
        // Feel free to adjust when you want pagination to be triggered
        if (indexPath.row == postArray.count - 1) {
            paginate()
        }
    }
}
Result like so:
Here is a reference.
My solution was similar to @yambo, however, I tried to avoid making extra calls to the database. After the first call to the database, I get back 10 objects and when it is time to load the new page I kept a reference of how many objects and I checked if the count + 9 is in the range of my new count.
    @objc func LoadMore() {
    let oldCount = self.uploads.count
    guard shouldLoadMore else { return }
    self.db.getNextPage { (result) in
        switch result {
        case .failure(let err):
            print(err)
        case .success(let newPosts):
            self.uploads.insert(contentsOf: newPosts, at: self.uploads.count)
            if oldCount...oldCount+9 ~= self.uploads.count {
                self.shouldLoadMore = false
            }
            DispatchQueue.main.async {
                self.uploadsView.collectionView.reloadData()
            }
        }
    }
}
                                                                        So here's the solution I've come up with! It is very likely that this solution makes multiple calls to firestore, creating a large bill for any real project, but it works as a proof of concept I guess you could say.
If you have any recommendations or edits, please feel free to share!
Here's how all the variables were initialized:
var rides = [Ride]()
var lastDocumentSnapshot: DocumentSnapshot!
var fetchingMore = false
If you have any recommendations or edits, please feel free to share!
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offsetY = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height
    //print("offsetY: \(offsetY) | contHeight-scrollViewHeight: \(contentHeight-scrollView.frame.height)")
    if offsetY > contentHeight - scrollView.frame.height - 50 {
        // Bottom of the screen is reached
        if !fetchingMore {
            paginateData()
        }
    }
}
// Paginates data
func paginateData() {
    fetchingMore = true
    var query: Query!
    if rides.isEmpty {
        query = db.collection("rides").order(by: "price").limit(to: 6)
        print("First 6 rides loaded")
    } else {
        query = db.collection("rides").order(by: "price").start(afterDocument: lastDocumentSnapshot).limit(to: 4)
        print("Next 4 rides loaded")
    }
    query.getDocuments { (snapshot, err) in
        if let err = err {
            print("\(err.localizedDescription)")
        } else if snapshot!.isEmpty {
            self.fetchingMore = false
            return
        } else {
            let newRides = snapshot!.documents.compactMap({Ride(dictionary: $0.data())})
            self.rides.append(contentsOf: newRides)
            //
            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
                self.tableView.reloadData()
                self.fetchingMore = false
            })
            self.lastDocumentSnapshot = snapshot!.documents.last
        }
    }
}