简体   繁体   English

使用 Swift 进行 Firebase Firestore 分页

[英]Firebase Firestore Pagination with Swift

With my app i tried to paginate my data (10 posts per page) from Firestore using below code,使用我的应用程序,我尝试使用以下代码从 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()


        }

    }


}

But i have faced the following issues :但我遇到了以下问题:

  • if i start to post something for the first time (FireStore has no data at all) from other devices the app will crash because the page will always be nil .如果我第一次开始从其他设备发布内容(FireStore 根本没有数据),应用程序将崩溃,因为页面将始终为nil
  • I tried to insert 10 post by the console and check the app when I start scrolling down with my table view it will crash for the same reason page is nil .我试图通过控制台插入 10 个帖子,并在我开始使用表格视图向下滚动时检查应用程序,它会因为同样的原因而崩溃pagenil

I'm wondering why is this happening although I'm saving the last Sanpshot document as pagination cursor !我想知道为什么会发生这种情况,尽管我将最后一个 Sanpshot 文档保存为分页光标! is there a better why to implement the pagination with Swift有没有更好的理由用 Swift 实现分页

Paginating in Firestore with Swift in iOS is very straightforward if we get documents statically (using getDocuments ) and not dynamically (through a snapshot listener).如果我们静态获取文档(使用getDocuments )而不是动态获取文档(通过快照侦听器),则在 iOS 中使用 Swift 在 Firestore 中进行分页非常简单。

We can still paginate with a snapshot listener but it requires more steps.我们仍然可以使用快照侦听器进行分页,但需要更多步骤。 That is because when a snapshot listener returns, it will return a single page of documents, and if the user has paginated through multiple pages, the update will reset the user back to a single page (this is not a very pleasant user experience).那是因为当快照监听器返回时,它会返回单页文档,如果用户分页了多个页面,更新会将用户重置回单页(这不是一个非常愉快的用户体验)。 But the workaround for this is also relatively straightforward (all we do is keep track of how many pages are rendered on screen and load that many underneath the user when a snapshot listener returns before refreshing the UI).但解决这个问题的方法也相对简单(我们所做的就是跟踪屏幕上呈现的页面数量,并在刷新 UI 之前快照侦听器返回时在用户下方加载多少页面)。

I think it's wise to split the loading of data (the first page) from the continuing of data (the additional pages) for readability.我认为将数据的加载(第一页)与数据的连续加载(附加页)分开以提高可读性是明智的。 firestoreQuery is a Query object that you must obviously construct on your own. 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()
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM