使用 Swift 进行 Firebase Firestore 分页

Posted

技术标签:

【中文标题】使用 Swift 进行 Firebase Firestore 分页【英文标题】:Firebase Firestore Pagination with Swift 【发布时间】:2018-04-23 12:49:35 【问题描述】:

在我的应用中,我尝试使用以下代码从 Firestore 对我的数据(每页 10 个帖子)进行分页,

import UIKit
import FirebaseFirestore
class Home: UITableViewController 


    var postArray = [postObject]()
    let db = Firestore.firestore()
    var page : DocumentSnapshot? = nil
    let pagingSpinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)

    override func viewDidLoad() 
        super.viewDidLoad()

       loadFirstPage()
    


    func loadFirstPage()

        // Get the first 10 posts

        db.collection("POSTS").limit(to: 10).addSnapshotListener  (snapshot, error) in
            if snapshot != nil 
                self.postArray = (snapshot?.documents.flatMap(postObject(dec : $0.data())))!

                // Save the last Document

                self.page = snapshot?.documents.last
                self.tableView.reloadData()
            
        
    

    func loadNextPage()

       // get the next 10 posts

        db.collection("POSTS").limit(to: 10).start(afterDocument: page!).addSnapshotListener  (snapshot, error) in
            if snapshot != nil 

                for doc in (snapshot?.documents)! 

                    self.postArray.append(postObject(dec: doc.data()))
                

                self.page = snapshot?.documents.last

                self.tableView.reloadData()

            

        

    


    override func numberOfSections(in tableView: UITableView) -> Int 

        return 1
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 

        return postArray.count
    


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as? postCell

        // display data

        cell?.textLabel?.text = postArray[indexPath.row].name

        return cell!
    


    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) 

        // check index to load next page

        if indexPath.row < (self.postArray.count)


            pagingSpinner.startAnimating()
            pagingSpinner.color = UIColor.red
            pagingSpinner.hidesWhenStopped = true
            tableView.tableFooterView = pagingSpinner
            loadNextPage()


        

    



但我遇到了以下问题:

如果我第一次开始发布内容(FireStore 没有 数据)来自其他设备的应用程序将崩溃,因为 page 将始终为 nil。 我尝试通过控制台插入 10 个帖子并在我检查应用程序时 开始使用我的表格视图向下滚动它会崩溃相同 原因 pagenil

我想知道为什么会发生这种情况,尽管我将最后一个 Sanpshot 文档保存为分页光标!有没有更好的为什么用 Swift 实现分页

【问题讨论】:

我最初的印象可能是您遇到了竞争情况,您的应用可能会在完成获取第一个快照之前尝试调用“loadNextPage”(因此页面将为 nil)。尝试添加一些控制台日志以查看是否是这种情况。 @ToddKerpelman 检查问题后,正如您提到的那样,滚动带有条件 if indexPath.row &lt; (self.postArray.count) 的 uitableview 将始终在获取第一个快照之前触发“loadNextPage” 【参考方案1】:

如果我们手动(使用getDocuments)而不是自动(使用addSnapshotListener)获取文档,使用Swift 在Firestore 中分页非常简单。

为了可读性,我认为将数据的加载(第一页)与数据的继续(附加页面)分开是明智的。 firestoreQuery 是一个 Query 对象,显然您必须自己构建。

class SomeViewController: UIViewController 
    private var cursor: DocumentSnapshot?
    private let pageSize = 10 // use this for the document-limit value in the query
    private var dataMayContinue = true
    
    /* This method grabs the first page of documents. */
    private func loadData() 
        firestoreQuery.getDocuments(completion:  (snapshot, error) in
            ...
            /* At some point after you've unwrapped the snapshot,
               manage the cursor. */
            if snapshot.count < pageSize 
                /* This return had less than 10 documents, therefore
                   there are no more possible documents to fetch and
                   thus there is no cursor. */
                self.cursor = nil
             else 
                /* This return had at least 10 documents, therefore
                   there may be more documents to fetch which makes
                   the last document in this snapshot the cursor. */
                self.cursor = snapshot.documents.last
            
            ...
        )
    
    
    /* This method continues to paginate documents. */
    private func continueData() 
        guard dataMayContinue,
              let cursor = cursor else 
            return
        
        dataMayContinue = false /* Because scrolling to bottom will cause this method to be called
                                   in rapid succession, use a boolean flag to limit this method
                                   to one call. */

        firestoreQuery.start(afterDocument: cursor).getDocuments(completion:  (snapshot, error) in
            ...
            /* Always update the cursor whenever Firestore returns
             whether it's loading data or continuing data. */
            if snapshot.count < self.pageSize 
                self.cursor = nil
             else 
                self.cursor = snapshot.documents.last
            
            ...
            /* Whenever we exit this method, reset dataMayContinue to true. */
        )
    


/* Let's assume you paginate with infinite scroll which means continuing data
   when the user scrolls to the bottom of the table or collection view. */
extension SomeViewController 
    /* Standard scroll-view delegate */
    func scrollViewDidScroll(_ scrollView: UIScrollView) 
        let contentSize = scrollView.contentSize.height
        
        if contentSize - scrollView.contentOffset.y <= scrollView.bounds.height 
            didScrollToBottom()
        
    
    
    private func didScrollToBottom() 
        continueData()
    

我们仍然可以使用快照侦听器进行分页,但它需要更多步骤。这是因为当快照侦听器返回时,它将返回单页文档,如果用户已分页浏览多个页面,则更新会将用户重置为单页。补救措施是跟踪在屏幕上呈现了多少页面,并在刷新 UI 之前快照侦听器返回时在用户下方加载这些页面。但是,除此之外还有其他步骤,例如在 UI 刷新期间处理新的快照返回;这种竞争条件需要严格的序列化,这可能需要也可能不需要信号量或其他有效的东西。

【讨论】:

以上是关于使用 Swift 进行 Firebase Firestore 分页的主要内容,如果未能解决你的问题,请参考以下文章

当应用程序关闭时,Rest Api Firebase 通知在 Swift 4 中不起作用

使用@angular/fire@6.0.3 无法访问firebase.firestore.FieldValue.serverTimestamp()

如何修复:“@angular/fire”'没有导出成员 'AngularFireModule'.ts(2305) ionic、firebase、angular

Firebase 存储上传图像不适用于亚马逊火灾(型号:Fire 7)

node_modules/@angular/fire/firebase.app.module.d.ts 中的错误?

使用 Firebase (Swift) 进行单元测试