1

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)
    }
}
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 1
    Does this answer your question? [SwiftUI Combine - I want to load data from api page by page](https://stackoverflow.com/questions/74411394/swiftui-combine-i-want-to-load-data-from-api-page-by-page) or this example code: https://stackoverflow.com/questions/74084510/swiftui-onappear-not-firing-as-expected-when-i-load-a-new-page-of-visible-json/74091573#74091573 – workingdog support Ukraine Jan 04 '23 at 02:49
  • unfortunately it does not – bruce_blake Jan 04 '23 at 12:45
  • You've tagged the question with Firebase Realtime Database but the code appears be be using Firestore so that's a little confusing. Then, there's an entire section in the Firebase Getting Started Guide that has examples of how to [Paginate data with query cursors](https://firebase.google.com/docs/firestore/query-data/query-cursors). Does that help? – Jay Jan 06 '23 at 21:12

0 Answers0