I am trying to add pagination to my chat app using swiftui and firebase. I want to listen to the newest 20 messages and as soon as the user scrolls to the top, fetch the oldest messages and display them at the top (previous messages).
this is my current code but it doesn't work like it's supposed to. Also the scrollview will just occasionally not scroll.
func loadMoreMessages(chatID: String){
guard !isLoading, hasMoreMessages else { return }
isLoading = true
COLLECTION_PERSONAL_CHAT.document(chatID).collection("Messages").order(by: "timeStamp", descending: false).start(afterDocument: firstSnapshot!).limit(to: 20).getDocuments { (snapshot, error) in
if let error = error {
print("Error fetching messages: \(error)")
self.isLoading = false
return
}
var messagesToAppend : [Message] = []
let dp = DispatchGroup()
var documents = snapshot!.documents
for document in documents {
dp.enter()
var data = document.data()
let id = data["id"] as? String ?? ""
let type = data["type"] as? String ?? ""
let value = data["value"] as? String ?? ""
let repliedMessageID = data["repliedMessageID"] as? String ?? ""
if type == "repliedMessage"{
dp.enter()
self.fetchReplyMessages(chatID: chatID, messageID: repliedMessageID) { fetchedReplyMessage in
data["repliedMessage"] = fetchedReplyMessage
dp.leave()
}
}
if type == "postMessage"{
dp.enter()
self.fetchPost(postID: value){ fetchedPost in
data["post"] = fetchedPost
dp.leave()
}
}
if type == "pollMessage"{
dp.enter()
self.fetchPoll(pollID: value){ fetchedPoll in
data["poll"] = fetchedPoll
dp.leave()
}
}
if type == "eventMessage"{
dp.enter()
self.fetchEvent(eventID: value){ fetchedEvent in
data["event"] = fetchedEvent
dp.leave()
}
}
dp.leave()
dp.notify(queue: .main, execute:{
self.isLoading = false
self.messages.insert(Message(dictionary: data), at: 0)
print("loaded 1 more message!")
})
}
}
}
func fetchAllMessages(chatID: String, userID: String){
//how to paginate
//1. listen to newest 20 messages [20,19,18,17,...,0]
//2. fetch 20 starting after the 20th
self.messages.removeAll()
self.chatListener = COLLECTION_PERSONAL_CHAT.document(chatID).collection("Messages").order(by: "timeStamp", descending: true).limit(to: 20).addSnapshotListener { snapshot, err in
if err != nil {
print(err!.localizedDescription)
return
}
var messagesToReturn : [Message] = []
let dp = DispatchGroup()
snapshot?.documentChanges.forEach({ doc in
dp.enter()
var data = doc.document.data()
let id = data["id"] as? String ?? ""
let type = data["type"] as? String ?? ""
let value = data["value"] as? String ?? ""
let repliedMessageID = data["repliedMessageID"] as? String ?? ""
if type == "repliedMessage"{
dp.enter()
self.fetchReplyMessages(chatID: chatID, messageID: repliedMessageID) { fetchedReplyMessage in
data["repliedMessage"] = fetchedReplyMessage
dp.leave()
}
}
if type == "postMessage"{
dp.enter()
self.fetchPost(postID: value){ fetchedPost in
data["post"] = fetchedPost
dp.leave()
}
}
if type == "pollMessage"{
dp.enter()
self.fetchPoll(pollID: value){ fetchedPoll in
data["poll"] = fetchedPoll
dp.leave()
}
}
if type == "eventMessage"{
dp.enter()
self.fetchEvent(eventID: value){ fetchedEvent in
data["event"] = fetchedEvent
dp.leave()
}
}
dp.leave()
dp.notify(queue: .main, execute:{
if doc.type == .added {
if !self.messages.contains(where: {$0.id == id}){
self.messages.append(Message(dictionary: data))
}
}else if doc.type == .removed{
self.messages.removeAll(where: {$0.id == id})
}else if let index = self.messages.firstIndex(where: {$0.id == id}) {
self.messages[index] = Message(dictionary: data)
}
})
//end of foreach document changes
})
self.firstSnapshot = snapshot!.documents.first
}
}
ScrollView{
ScrollViewReader { scrollViewProxy in
LazyVStack(spacing: 0){
ForEach(personalChatVM.messages.reversed().indices, id: \.self){ index in
MessageCell(message: personalChatVM.messages[index], selectedMessage: $selectedMessage,
showOverlay: $showOverlay, personalChatVM: personalChatVM).onAppear{
if index == 0{
print("seen top")
}
}
}
VStack{
ForEach(personalChatVM.chat.usersTyping){ user in
HStack{
if user.id == userVM.user?.id ?? " "{
Text("You are typing").foregroundColor(Color("AccentColor")).bold()
}else{
Text("\(user.nickName ?? "") is typing...").foregroundColor(Color("AccentColor")).bold()
}
Spacer()
}.padding(5)
}
}
HStack{Spacer()}.padding(0).id("Empty")
}.padding(.bottom, self.keyboardHeight)
}
}