如何在滚动到顶部同时添加侦听器时对从 Firestore 获取的聊天进行分页

Posted

技术标签:

【中文标题】如何在滚动到顶部同时添加侦听器时对从 Firestore 获取的聊天进行分页【英文标题】:How to paginate chat fetched from firestore while scrolling to the top while also adding a listener 【发布时间】:2019-10-11 18:07:50 【问题描述】:

我是 swift 新手,我通过观看教程成功地构建了一个聊天应用程序。现在,当我单击一个用户时,它会将我带到聊天控制器,并且它能够成功获取消息。一切正常。我需要实现的是能够在我滚动到顶部时对聊天进行分页。现在每次打开聊天室时我都会获取所有消息。

我确实查看了有关如何执行此操作的 google firestore 文档,但对于我的生活,我无法将其拼凑起来。对它在理论上是如何工作的有一个公平的想法。但是由于我添加了快照侦听器,所以有点令人困惑。

也不知道如何配置滚动​​视图在到达顶部时加载

class ChatLogController: UICollectionViewController, UICollectionViewDelegateFlowLayout 

//MARK: Initialize

fileprivate let connect: Connect
var listener: ListenerRegistration?
var currentUser: User?

init(connect: Connect)
    self.connect = connect
    super.init()
   

//MARK: ****View Did Load****

override func viewDidLoad() 
    super.viewDidLoad()
    fetchCurrentUser()
    fetchPaginatedMessages()
    setupView()


//MARK: Fetch Current User

fileprivate func fetchCurrentUser()
       //Some code  
        print("Current User Fetched Successfully")
        self.currentUser = User(dictionary: data)
    


//MARK: Setup View
//Some code that fetches the front end


//MARK: FetchPaginatedMessages - Code does not contain attempted pagination

fileprivate func fetchPaginatedMessages()

    print("Fetching Messages")
    guard let cUid = Auth.auth().currentUser?.uid else return

    let query = Firestore.firestore().collection("matches").document(cUid).collection(connect.uid).order(by: "Timestamp")

    listener = query.addSnapshotListener  (querySnapshot, error) in
        if let error = error
            print("There was an error fetching messages", error.localizedDescription)
            return
        

        querySnapshot?.documentChanges.forEach( (change) in
            if change.type == .added
                let dictionary = change.document.data()
                self.items.append(.init(dictionary: dictionary))
                print("ARCHID ---- FIRESTORE HAS BEEN CONTACTED FETCHING MESSAGES")
            
        )

        self.collectionView.reloadData()

        self.collectionView.scrollToItem(at: [0, self.items.count - 1], at: .bottom, animated: true)
        print("Fetched messages")
    

包括代码的主要部分。为了便于阅读,省略了不相关的代码。 func fetchPaginatedMessages 是我打算进行分页的地方。

【问题讨论】:

分页是 Firestore 可以通过使用 .startAt 和/或 .endAt(查询游标)来完成。入门指南Paginate data with query cursors 中有一些非常好的文档。您还可以将文档快照传递给游标以定义查询游标的起点或终点。我没有看到您在代码中实际在哪里进行 Firestore 分页 - 也许我忽略了它或误解了您的要求。你能澄清一下吗? 哦... Ray Wenderlich 教程是必读的。 UITableView Infinite Scrolling Tutorial 如果你想了解无限滚动。 您好,Jay 感谢您的评论。我实际上没有做任何分页。我刚刚命名了这个函数,以表明它是用来进行分页的。是的,在我发布到 *** 之前,我确实阅读了链接中的文章,但我无法理解它是如何工作的,尤其是当我使用侦听器时 嗯。您的问题是如何分页,这意味着您在问如何分页....但您没有进行分页?意思是你不想做分页或者你不知道如何去实现代码? @Jay 我的意思是我试图在 firebase 文档中执行代码,但无法让它工作。另外,当我位于滚动顶部时,我无法理解如何触发分页。所以我最终只是发布了我如何接收所有消息以及如何将其转换为分页。 【参考方案1】:

我自己为分页挣扎了很长时间,因为当用户到达底部时每个人都在加载更多,这很容易,你可以在 *** 上找到解决方案,但是没有人实现加载之前的事情(意味着当用户滚动到顶部时并拉动以刷新旧消息,因为在聊天中您必须先显示最新消息,然后再显示旧消息,并且您的滚动也会向后滚动,因为最新消息在底部,最旧的消息在顶部)。 经过这么多努力,我发现这个解决方案运行良好。

首先,您正在实现快照侦听器,它将为您提供该表(文档)中的所有消息。你必须稍微改变一下。首次进入聊天屏幕时忽略所有数据(我知道它看起来很愚蠢,但它有效)。因为新消息需要快照侦听器,否则您不会收到最新消息。

然后对文档进行分页。这是我的解决方案字段名称可能会有所不同。

另外,我正在使用 MessageKit 来显示聊天 UI,您可以进行相应更改。 这是我用于分页的聊天视图控制器,您可以根据需要进行更改。 当用户触发拉动以自动刷新时,我的代码会拉出较旧的消息,您可以找到无限滚动到顶部的代码以满足您的要求。 谢谢。

class ChatViewController1: MessagesViewController 

    // MARK: - Variables -
    var messages: [Message] = []
    var pageSize: Int = 25
    var handles: [ListenerRegistration] = []
    var lastSnapShot: QueryDocumentSnapshot?
    var msgNode = "Your Table Name"
    
    private(set) lazy var refreshControl: UIRefreshControl = 
        let control = UIRefreshControl()
        control.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
        return control
    ()
    
    // MARK: - Life Cycle -
    override func viewDidLoad() 
        super.viewDidLoad()
        
        messagesCollectionView.refreshControl = refreshControl
        self.setupChat()
    
    
    override func viewWillDisappear(_ animated: Bool) 
        super.viewWillDisappear(animated)
        
        for item in handles 
            
            item.remove()
        
    
    
    // MARK: - Setup Methods -
    @objc func loadMoreMessages() 
        self.getChat(lastSnapShot)
    
    
    func setupChat() 
        FIRDBManager.shared.db
            .collection(self.msgNode)
            .order(by: "messageTime", descending: false).addSnapshotListener(includeMetadataChanges: true)  snapshot, error in
                if let error = error
                    print("There was an error fetching messages", error.localizedDescription)
                    return
                
                // I am checking if messages already exist then it means this is new message or earlier message updated / deleted so handle it that way.
                if self.messages.count > 0 
                    snapshot?.documentChanges.forEach( (change) in
                        let dictionary = change.document.data()
                        
                        let msg = Message(fromDictionary: dictionary)
                        
                        if change.type == .added 
                            if !self.messages.contains(msg) 
                                self.messages.append(msg)
                            
                         else if change.type == .removed 
                            self.messages.removeAll(where:  $0.messageId == msg.messageId )
                         else if let index = self.messages.firstIndex(of: msg) 
                            
                            self.messages[index] = msg
                        
                    )
                    DispatchQueue.main.async 
                        self.messagesCollectionView.reloadData()
                        self.messagesCollectionView.scrollToLastItem()
                    
                
            
        
        self.getChat(nil)
    
    
    func getChat(_ snap: QueryDocumentSnapshot?) 
        
        let query: Query
        
        if let _ = snap 
            query = FIRDBManager.shared.db
                .collection(self.msgNode)
                .order(by: "messageTime", descending: false)
                .end(at: [messages.first!.messageTime!])
                .limit(toLast: self.pageSize)
            
            //end(atDocument: s)
         else 
            query = FIRDBManager.shared.db
                .collection(self.msgNode)
                .order(by: "messageTime", descending: false)
                .limit(toLast: self.pageSize)
        
        
        query.getDocuments  (snapshot, err) in
            if let e = err 
                print("Error fetching document: \(e.localizedDescription)")
                self.refreshControl.endRefreshing()
             else if snapshot!.isEmpty 
                print("Document is empty")
                self.refreshControl.endRefreshing()
             else 
                self.updateMessages(snapshot!)
            
        
    
    
    fileprivate func updateMessages(_ snapshot: QuerySnapshot) 
        if snapshot.documents.count <= 1 
            self.refreshControl.endRefreshing()
            return
        
        var allMessages: [Message] = []
        
        snapshot.documents.forEach  (item) in
            if item.documentID != "DeviceIds" 
                let msg = Message(fromDictionary: item.data())
                allMessages.append(msg)
            
        
        
        DispatchQueue.main.async 
            if self.messages.count > 0 
                self.messages.insert(contentsOf: allMessages, at: 0)
                self.messagesCollectionView.reloadDataAndKeepOffset()
                self.refreshControl.endRefreshing()
             else 
                self.messages = allMessages
                self.messagesCollectionView.reloadData()
                self.messagesCollectionView.scrollToLastItem()
            
            self.lastSnapShot = snapshot.documents.last
        
    

【讨论】:

以上是关于如何在滚动到顶部同时添加侦听器时对从 Firestore 获取的聊天进行分页的主要内容,如果未能解决你的问题,请参考以下文章

添加滚动到顶部按钮(Ionic 2 | Typescript)

滚动时如何让侧边栏对齐到顶部?

iOS滚动视图禁用自动滚动到顶部

滚动到顶部时删除类

禁用 UITextView 中的滚动,同时避免在 ios 的当前位置弹跳到顶部和文本不可见

滚动到顶部时在表格视图的开头动态添加行