使用 Firebase Firestore 进行分页 - swift 4

Posted

技术标签:

【中文标题】使用 Firebase Firestore 进行分页 - swift 4【英文标题】:Pagination with Firebase firestore - swift 4 【发布时间】:2019-02-11 00:12:04 【问题描述】:

我正在尝试使用 Firestore 对数据进行分页(无限滚动我的表格视图)。我已尽我所能集成了谷歌为分页提供的代码,但我仍然无法正确加载数据。

初始数据集根据需要加载到 tableview 中。然后,当用户点击屏幕底部时,下一个“x”数量的项目被加载。但是当用户第二次点击屏幕底部时,相同的“x”项目被简单地附加到表格中看法。相同的项目会无限期地添加。

所以它最初的 3 个“骑行”对象,接下来的 4 个“骑行”对象永远重复。

123 4567 4567 4567 4567...

如何让数据正确加载?

func scrollViewDidScroll(_ scrollView: UIScrollView) 
    let offsetY = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height

    if offsetY > contentHeight - scrollView.frame.height 
        // Bottom of the screen is reached
        if !fetchingMore 
            beginBatchFetch()
        
    


func beginBatchFetch() 
    // Array containing "Ride" objcets is "rides"

    fetchingMore = true

    // Database reference to "rides" collection
    let ridesRef = db.collection("rides")

    let first = ridesRef.limit(to: 3)

    first.addSnapshotListener  (snapshot, err) in
        if let snapshot = snapshot 
            // Snapshot isn't nil
            if self.rides.isEmpty 
                // rides array is empty (initial data needs to be loaded in).
                let initialRides = snapshot.documents.compactMap(Ride(dictionary: $0.data()))
                self.rides.append(contentsOf: initialRides)
                self.fetchingMore = false
                DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: 
                    self.tableView.reloadData()
                )
                print("first rides loaded in")
            
         else 
            // Error
            print("Error retreiving rides: \(err.debugDescription)")
            return
        

        // reference to lastSnapshot
        guard let lastSnapshot = snapshot!.documents.last else
            // The collection is empty
            return
        


        let next = ridesRef.limit(to: 4).start(afterDocument: lastSnapshot)

        next.addSnapshotListener( (snapshot, err) in
            if let snapshot = snapshot 

                if !self.rides.isEmpty 

                    let newRides = snapshot.documents.compactMap(Ride(dictionary: $0.data()))
                    self.rides.append(contentsOf: newRides)
                    self.fetchingMore = false
                    DispatchQueue.main.asyncAfter(deadline: .now() + 7, execute: 
                        self.tableView.reloadData()
                    )

                    print("new items")
                    return
                
             else 
                print("Error retreiving rides: \(err.debugDescription)")
                return
            

        )
    

【问题讨论】:

【参考方案1】:

这就是我想出的解决方案!该解决方案很可能会多次调用 firestore,从而为任何实际项目创建大笔账单,但我猜你可以说它是一个概念证明。

如果您有任何建议或修改,请随时分享!

所有变量的初始化方式如下:

var rides = [Ride]()
var lastDocumentSnapshot: DocumentSnapshot!
var fetchingMore = false

如果您有任何建议或修改,请随时分享!

func scrollViewDidScroll(_ scrollView: UIScrollView) 
    let offsetY = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height
    //print("offsetY: \(offsetY) | contHeight-scrollViewHeight: \(contentHeight-scrollView.frame.height)")
    if offsetY > contentHeight - scrollView.frame.height - 50 
        // Bottom of the screen is reached
        if !fetchingMore 
            paginateData()
        
    


// Paginates data
func paginateData() 

    fetchingMore = true

    var query: Query!

    if rides.isEmpty 
        query = db.collection("rides").order(by: "price").limit(to: 6)
        print("First 6 rides loaded")
     else 
        query = db.collection("rides").order(by: "price").start(afterDocument: lastDocumentSnapshot).limit(to: 4)
        print("Next 4 rides loaded")
    

    query.getDocuments  (snapshot, err) in
        if let err = err 
            print("\(err.localizedDescription)")
         else if snapshot!.isEmpty 
            self.fetchingMore = false
            return
         else 
            let newRides = snapshot!.documents.compactMap(Ride(dictionary: $0.data()))
            self.rides.append(contentsOf: newRides)

            //
            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: 
                self.tableView.reloadData()
                self.fetchingMore = false
            )

            self.lastDocumentSnapshot = snapshot!.documents.last
        
    

【讨论】:

【参考方案2】:

我的解决方案类似于@yambo,但是,我尽量避免对数据库进行额外调用。在第一次调用数据库后,我得到了 10 个对象,当该加载新页面时,我保留了多少对象的引用,并检查了 count + 9 是否在我的新计数范围内。

    @objc func LoadMore() 
    let oldCount = self.uploads.count
    guard shouldLoadMore else  return 
    self.db.getNextPage  (result) in
        switch result 
        case .failure(let err):
            print(err)
        case .success(let newPosts):
            self.uploads.insert(contentsOf: newPosts, at: self.uploads.count)
            if oldCount...oldCount+9 ~= self.uploads.count 
                self.shouldLoadMore = false
            
            DispatchQueue.main.async 
                self.uploadsView.collectionView.reloadData()
            
        
    

【讨论】:

【参考方案3】:

游戏有点晚了,但我想分享一下我是如何做到的,使用 query.start(afterDocument:) 方法。

class PostsController: UITableViewController 

    let db = Firestore.firestore()

    var query: Query!
    var documents = [QueryDocumentSnapshot]()
    var postArray = [Post]()

    override func viewDidLoad() 
        super.viewDidLoad()

        query = db.collection("myCollection")
                  .order(by: "post", descending: false)
                  .limit(to: 15)

        getData()
    

    func getData() 
        query.getDocuments()  (querySnapshot, err) in
            if let err = err 
                print("Error getting documents: \(err)")
             else 
                querySnapshot!.documents.forEach( (document) in
                    let data = document.data() as [String: AnyObject]

                    //Setup your data model

                    let postItem = Post(post: post, id: id)

                    self.postArray += [postItem]
                    self.documents += [document]
                )
                self.tableView.reloadData()
            
         
    

    func paginate() 
        //This line is the main pagination code.
        //Firestore allows you to fetch document from the last queryDocument
        query = query.start(afterDocument: documents.last!)
        getData()
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return postArray.count
    

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) 
        // Trigger pagination when scrolled to last cell
        // Feel free to adjust when you want pagination to be triggered
        if (indexPath.row == postArray.count - 1) 
            paginate()
        
    

结果如下:

这是reference。

【讨论】:

我见过的手动 UITableView 分页的最佳答案。 这是一个很好的答案。我遇到了此处使用的 WillDisplay 的问题,因此我选择使用 scrollViewDidScroll(例如:***.com/a/41255637/9339880),并添加一个小延迟以避免底部检测重复,但这是一个很好的基础。【参考方案4】:

简单、快速、简单的方法是......

类 FeedViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, FeedCellDelegate

private var quotes = [Quote]() 
    
    didSet tbl_Feed.reloadData() 


var quote: Quote?
var fetchCount = 10

@IBOutlet weak var tbl_Feed: UITableView!

override func viewDidLoad() 
    super.viewDidLoad()

    fetchPost() 



// MARK: - API

func fetchPost() 
    
    reference(.Quotes).limit(to: getResultCount).getDocuments  (snapshot, error) in
        
        guard let documents = snapshot?.documents else  return 
        
        documents.forEach  (doc) in
            
            let quotes = documents.map (Quote(dictionary: $0.data()))
            
            self.quotes = quotes
        
    
  

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) 
    
    let currentOffset = scrollView.contentOffset.y
    let maxxOffset = scrollView.contentSize.height - scrollView.frame.size.height
    
    if maxxOffset - currentOffset <= 300  // Your cell size 300 is example

        fetchCount += 5
        fetchPost()

        print("DEBUG: Fetching new Data")
    

【讨论】:

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

如何使用 ReactJs 对 Cloud Firestore 数据进行分页

使用 Swift 进行 Firebase Firestore 分页

使用streambuilder颤动firestore分页

Firestore 分页 - 是不是有任何与 firebase 的 limitToLast 兼容的查询?

如何在 Angular(6) 和 firebase Firestore 设置中分页返回前一页

如何使用 Firebase Firestore 进行一次性简单查询?