【问题标题】:Swift iOS: Firebase PagingSwift iOS:Firebase 分页
【发布时间】:2016-09-22 16:31:41
【问题描述】:

我有这个 Firebase 数据:

我想通过分页查询posts数据。目前我的代码正在将此JS 代码转换为 Swift 代码

let postsRef = self.rootDatabaseReference.child("development/posts")
postsRef.queryOrderedByChild("createdAt").queryStartingAtValue((page - 1) * count).queryLimitedToFirst(UInt(count)).observeSingleEventOfType(.Value, withBlock: { snapshot in
....

  })

访问时,此数据page: 1, count: 1。我可以获取“posts.a”的数据,但是当我尝试访问 page: 2, count: 1 时,返回仍然是“posts.a”

我在这里错过了什么?

【问题讨论】:

  • 存在逻辑问题。对于 page: 1, count: 1,查询从 0 开始,返回 1 个帖子。对于 page: 2, count: 1,查询从 1 开始,返回 1 个帖子。两者都基于 createdAt 节点,该节点的值都大于 1,因此它只会返回第一个帖子。

标签: ios swift firebase firebase-realtime-database


【解决方案1】:

假设您在将数据推送到 Firebase 时正在或将要使用 childByAutoId(),您可以使用 queryOrderedByKey() 按时间顺序排列数据。文档here.

唯一键基于时间戳,因此列表项将自动按时间顺序排列。

要从特定键开始,您必须在查询后面附加queryStartingAtValue(_:)。

示例用法:

var count = numberOfItemsPerPage

var query ref.queryOrderedByKey()

if startKey != nil {
  query = query.queryStartingAtValue(startKey)
  count += 1
}

query.queryLimitedToFirst(UInt(count)).observeSingleEventOfType(.Value, withBlock: { snapshot in
  guard var children = snapshot.children.allObjects as? [FIRDataSnapshot] else {
    // Handle error
    return
  }

  if startKey != nil && !children.isEmpty {
    children.removeFirst()
  }

  // Do something with children
})

【讨论】:

  • 降序如何?首先获得最新数据?
  • @KutakMir 我认为使用:queryEndingAtValue 和 queryLimitedToLast
  • @KutakMir 我认为@levantAJ 是对的。您可以使用这些方法,但您将传递的密钥应该不同。您将使用第一项的密钥,而不是使用 Firebase 返回的最后一项的密钥。也不要使用removeFirst(),而是使用removeLast()。我现在无法对此进行测试,因此您应该自己尝试一下。 :)
  • @Vats 每个快照都有一个键。 (snapshot.key) 这是记录本身的 id。您所做的就是保存第一个或最后一个。 :)
  • 请解释或显示更多代码。如果你们不介意...与火力分页作斗争会让我感到困惑
【解决方案2】:

我知道我来晚了,timominous 的回答很好,但我想分享一下我解决这个问题的方法。这是一个完整的例子,它不仅仅是关于分页。这个例子在 Swift 4 中,我使用了一个名为 CodableFirebase 的不错的库(你可以找到它here)来解码 Firebase 快照值。

除此之外,请记住在创建帖子并将该键存储在 postId(或您的变量)中时使用 childByAutoId。所以,我们以后可以使用它。

现在,模型看起来是这样的......

    class FeedsModel: Decodable {
        var postId: String!
        var authorId: String! //The author of the post
        var timestamp: Double = 0.0 //We'll use it sort the posts.
        //And other properties like 'likesCount', 'postDescription'...
    }

我们将使用此功能以近期优先的方式获取帖子

    class func getFeedsWith(lastKey: String?, completion: @escaping ((Bool, [FeedsModel]?) -> Void)) {
        let feedsReference = Database.database().reference().child("YOUR FEEDS' NODE")
        let query = (lastKey != nil) ? feedsReference.queryOrderedByKey().queryLimited(toLast: "YOUR NUMBER OF FEEDS PER PAGE" + 1).queryEnding(atValue: lastKey): feedsReference.queryOrderedByKey().queryLimited(toLast: "YOUR NUMBER OF FEEDS PER PAGE")
        //Last key would be nil initially(for the first page).

        query.observeSingleEvent(of: .value) { (snapshot) in
            guard snapshot.exists(), let value = snapshot.value else {
                completion(false, nil)
                return
            }
            do {
                let model = try FirebaseDecoder().decode([String: FeedsModel].self, from: value)
                //We get the feeds in ['childAddedByAutoId key': model] manner. CodableFirebase decodes the data and we get our models populated.
                var feeds = model.map { $0.value }
                //Leaving the keys aside to get the array [FeedsModel]
                feeds.sort(by: { (P, Q) -> Bool in P.timestamp > Q.timestamp }) 
                //Sorting the values based on the timestamp, following recent first fashion. It is required because we may have lost the chronological order in the last steps.
                if lastKey != nil { feeds = Array(feeds.dropFirst()) }
                //Need to remove the first element(Only when the lastKey was not nil) because, it would be the same as the last one in the previous page.
                completion(true, feeds)
                //We get our data sorted and ready here.
            } catch let error {
                print("Error occured while decoding - \(error.localizedDescription)")
                completion(false, nil)
            }
        }
    }

现在,在我们的 viewController 中,对于初始加载,函数调用在 viewDidLoad 中是这样的。当 tableView 将显示单元格时,将获取下一页...

    class FeedsViewController: UIViewController {

        //MARK: - Properties
        @IBOutlet weak var feedsTableView: UITableView!

        var dataArray = [FeedsModel]()
        var isFetching = Bool()
        var previousKey = String()
        var hasFetchedLastPage = Bool()


        //MARK: - ViewController LifeCycle
        override func viewDidLoad() {
            super.viewDidLoad()
            //Any other stuffs..
            self.getFeedsWith(lastKey: nil) //Initial load.
        }

        //....

        func getFeedsWith(lastKey: String?) {

            guard !self.isFetching else {
                self.previousKey = ""
                return
            }
            self.isFetching = true

            FeedsModel.getFeedsWith(lastKey: lastKey) { (status, data) in
                self.isFetching = false
                guard status, let feeds = data else {
                    //Handle errors
                    return
                }

                if self.dataArray.isEmpty { //It'd be, when it's the first time.
                    self.dataArray = feeds
                    self.feedsTableView.reloadSections(IndexSet(integer: 0), with: .fade)
                } else {
                    self.hasFetchedLastPage = feeds.count < "YOUR FEEDS PER PAGE"
                    //To make sure if we've fetched the last page and we're in no need to call this function anymore.
                    self.dataArray += feeds
                   //Appending the next page's feed. As we're getting the feeds in the recent first manner.
                    self.feedsTableView.reloadData()
                }
            }
        }

        //MARK: - TableView Delegate & DataSource

        //....

        func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            if self.dataArray.count - 1 == indexPath.row && !self.hasFetchedLastPage {
            let lastKey = self.dataArray[indexPath.row].postId
            guard lastKey != self.previousKey else { return }
            //Getting the feeds with last element's postId. (postId would be the same as a specific node in YourDatabase/Feeds).
            self.getFeedsWith(lastKey: lastKey)
            self.previousKey = lastKey ?? ""
        }

        //....
    }

【讨论】:

  • 在启用离线模式的 Swift 中Database.database().isPersistenceEnabled = trueobserveSingleEvent 经常返回缓存数据(不是最新数据),有没有人有一个只用观察的解决方案?
  • Firebase 7.5.0 似乎已经修复了使用 isPersistenceEnabled 时获取缓存数据的问题。使用新引入的 getData 而不是 observeSingleEvent。
  • 感谢@JackChen 提供的信息。
猜你喜欢
  • 2017-07-20
  • 2017-04-13
  • 2018-08-21
  • 2016-05-23
  • 1970-01-01
  • 2017-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多