David
David

Reputation: 839

How to use Firestore query pagination with TableVIew

I am trying to use Firestore pagination with swift TableView. Here is my code which loads the first 4 posts from firestore.

func loadMessages(){
        let postDocs = db
            .collectionGroup("userPosts")
            .order(by: "postTime", descending: false)
            .limit(to: 4)

        postDocs.addSnapshotListener { [weak self](querySnapshot, error) in
            self?.q.async{
                self!.posts = []

                guard let snapshot = querySnapshot else {
                    if let error = error {
                        print(error)
                    }
                    return
                }

                guard let lastSnapshot = snapshot.documents.last else {
                    // The collection is empty.
                    return
                }

                let nextDocs = Firestore.firestore()
                    .collectionGroup("userPosts")
                    .order(by: "postTime", descending: false)
                    .start(afterDocument: lastSnapshot)

                if let postsTemp = self?.createPost(snapshot){

                    DispatchQueue.main.async {
                        self!.posts = postsTemp
                        self!.tableView.reloadData()
                    }
                }
            }
        }
    }

    func createPost(_ snapshot: QuerySnapshot) ->[Post]{
        var postsTemp = [Post]()
        for doc in snapshot.documents{
            if let firstImage = doc.get(K.FStore.firstImageField) as? String,
                let firstTitle = doc.get(K.FStore.firstTitleField) as? String,
                let secondImage = doc.get(K.FStore.secondImageField) as? String,
                let secondTitle = doc.get(K.FStore.secondTitleField) as? String,
                let userName = doc.get(K.FStore.poster) as? String,
                let uID = doc.get(K.FStore.userID) as? String,
                let postDate = doc.get("postTime") as? String,
                let votesForLeft = doc.get("votesForLeft") as? Int,
                let votesForRight = doc.get("votesForRight") as? Int,
                let endDate = doc.get("endDate") as? Int{
                let post = Post(firstImageUrl: firstImage,
                                secondImageUrl: secondImage,
                                firstTitle: firstTitle,
                                secondTitle: secondTitle,
                                poster: userName,
                                uid: uID,
                                postDate: postDate,
                                votesForLeft: votesForLeft,
                                votesForRight:votesForRight,
                                endDate: endDate)
                postsTemp.insert(post, at: 0)
            }else{

            }
        }
        return postsTemp
    }

Here is my delegate which also detects the end of the TableView:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let post = posts[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! PostCell
        cell.delegate = self

        let seconds = post.endDate
        let date = NSDate(timeIntervalSince1970: Double(seconds))
        let formatter = DateFormatter()
        formatter.dateFormat = "M/d h:mm"

        if(seconds <= Int(Date().timeIntervalSince1970)){
            cell.timerLabel?.text = "Voting Done!"
        }else{
            cell.timerLabel?.text = formatter.string(from: date as Date)
        }

        let firstReference = storageRef.child(post.firstImageUrl)
        let secondReference = storageRef.child(post.secondImageUrl)

        cell.firstTitle.setTitle(post.firstTitle, for: .normal)
        cell.secondTitle.setTitle(post.secondTitle, for: .normal)
        cell.firstImageView.sd_setImage(with: firstReference)
        cell.secondImageView.sd_setImage(with: secondReference)
        cell.userName.setTitle(post.poster, for: .normal)
        cell.firstImageView.layer.cornerRadius = 8.0
        cell.secondImageView.layer.cornerRadius = 8.0

        if(indexPath.row + 1 == posts.count){
            print("Reached the end")
        }

        return cell
    }

Previously I had an addSnapshotListener without a limit on the Query and just pulled down all posts as they came. However I would like to limit how many posts are being pulled down at a time. I do not know where I should be loading the data into my model. Previously it was being loaded at the end of the addSnapshotListener and I could still do that, but when do I use the next Query? Thank you for any help and please let me know if I can expand on my question any more.

Upvotes: 0

Views: 68

Answers (1)

Martijn
Martijn

Reputation: 470

There is a UITableViewDelegate method called tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) that will be called just before a cell is loading.

You could use this one to check if the row at IndexPath is in fact the cell of the last object in your tableview's datasource. Something like datasource.count - 1 == IndexPath.row (The -1 is to account for item 0 being the first item in an array, where as it already counts as 1).

If that object is indeed the last one in your datasource, you could make a call to Firebase and add items to the datasource. Before mutating the datasource, make sure to check the new number of objects the show (the ones already loaded + new ones) has to be larger than the current number of objects in the datasource, otherwise the app will crash.

You also might want to give your user a heads up that you're fetching data. You can trigger that heads up also in the delegate method.

Upvotes: 2

Related Questions