Reputation: 839
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
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