11

I'm trying to order my data from Firebase so the most recent post is at the top (like Instagram), but I just can't get it to work properly. Should I be using a server timestamp? Is there a "createdAt" field?

func getPosts() {
    POST_REF.observeEventType(.Value, withBlock: { snapshot in
        guard let posts = snapshot.value as? [String : [String : String]] else {
            print("No Posts Found")
            return
        }

        Post.feed?.removeAll()
        for (postID, post) in posts {
            let newPost = Post.initWithPostID(postID, postDict: post)!
            Post.feed?.append(newPost)
        }
        Post.feed? = (Post.feed?.reverse())!
        self.tableView.reloadData()
        }, withCancelBlock: { error in
            print(error.localizedDescription)
    })
}
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
kerbelda
  • 325
  • 1
  • 4
  • 14
  • See the [documentation on ordering (and filtering) data](https://www.firebase.com/docs/ios/guide/retrieving-data.html#section-queries). But be aware that there is no built-in way to get the items in descending order. But you could do that client-side, once you get the data. See [the answer to this question](http://stackoverflow.com/questions/36314374/swift-how-to-create-sort-query-as-descending-on-firebase) – Frank van Puffelen May 10 '16 at 16:43
  • As @FrankvanPuffelen states, there's no built in way to do it. However, if your Firebase data is structured right way, you can use Firebase to do it and not have to do it client side. See [The answer to this question](http://stackoverflow.com/questions/36589452/in-firebase-how-can-i-query-the-most-recent-10-child-nodes/36665442#36665442) – Jay May 10 '16 at 17:40

2 Answers2

13

Using only reverse() for your array is not enough way to encompass everything. There are different things you need to think about:

  • Limit while retrieving data, use append() and then reverse() to save time. You don't need to delete all array for each time.

  • Scroll trigger or willDisplay cell method loading

Let's start. You can create a child for your posts timestamp or date/time being global. To provide like Instagram seconds, weeks I advice you using UTC time. So I will call this: (timeUTC)

For sorting your all post, use since1970 timestamp. So I will call this (timestamp) and then also you can keep another node as (reversedTimestamp) adding - prefix to timestamp. So when you use queryOrdered to this node. You can handle latest 5 post using with yourQuery.queryLimited(toFirst: 5).

1.Get UTC date/time for timeUTC node in Swift 3:

        let date = Date()
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
        formatter.timeZone = TimeZone(abbreviation: "UTC")
        let utcTimeZoneStr = formatter.string(from: date)

+0000 means it's universal time, look at http://time.is/tr/UTC

2.Get since1970 timestamp to sort posts in Swift 3:

let timestamp = (Date().timeIntervalSince1970 as NSString).doubleValue
let reversedTimestamp = -1.0 * timestamp

Now, you can save them on your Firebase posts like this.

"posts" : {
    "-KHLOy5mOSq0SeB7GBXv" : {
      "timestamp": "1475858019.2306"
      "timeUTC" : "2012-02-04 12:11:56 +0000"
    },
    "-KHLrapS0wbjqPP5ZSUY" : {
      "timestamp": "1475858010.1245"
      "timeUTC" : "2014-02-04 12:11:56 +0000"
    },

I will retrieve five by five post, so I'm doing queryLimited(toFirst: 5) in viewDidLoad:

let yourQuery = ...queryOrdered(byChild: "reverseTimestamp")
                   .queryEnding(atValue: "\(self.pageOnTimestamp)", childKey: "reverseTimestamp")

    yourQuery.observeSingleEvent(of: .value, with: { (snapshot) in

        if snapshot.value is NSNull {

            print("There is no post.")

        }
        else {

            yourQuery.queryLimited(toFirst: 5).observeSingleEvent(of: .value, with: { (snapshot) in

                self.posts.removeAll(keepingCapacity: true)

                for (i, snap) in snapshot.children.enumerated() {

                    if let postAllDict = snapshot.value as? [String: AnyObject] {
                        if let postDict = postAllDict[(snap as AnyObject).key as String] as? [String: AnyObject] {

                            let post = Post(key: (snap as AnyObject).key as String, postDict: postDict)
                            self.posts.append(post)
                        }
                    }
                }

                completion(true)
            })
        }
    })

If user reached latest post, you can handle it with willDisplay method like below, then you can call loadMore function.

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

   if self.posts.count - 1 == indexPath.row {
      // call loadMore function.
   }
}

In loadMore() function you can handle latest post's timestamp, then start-end query as with that, so you can easily continue with next first 5 posts while appending before array.

For Swift 3 conversion as nice formatted, take a look here: Swift 3 - UTC to time ago label, thinking 12h / 24h device time changes

Community
  • 1
  • 1
iamburak
  • 3,508
  • 4
  • 34
  • 65
  • Thank you so much, this really helped and solved my issue. Also, I was looking into pagination, so that's awesome! Do you have to use single event for pagination? Because that won't automatically update the feed when new posts are submitted anymore. – kerbelda May 11 '16 at 16:53
  • @kerbelda I think observing every changes in descending order is not true approach for my explanation. It can effect user experience if user read a cell that case can trigger scroll. If you observe and then add my tableView array, you can see It won't work in proper way. Instead, you can add a navigation item for refreshing (call viewDidLoad: again go top of the screen) or check `self.tableView.contentOffset.y` is equal to 0 status in **scrollViewDidScroll()** and then you can call viewDidLoad: to load last first 5 post automatically. – iamburak May 11 '16 at 20:15
  • @tobeiosdev would you have an email I could contact you at? – Link Openheim Sep 26 '16 at 01:30
1

Based on @tobeiosdev answer i define my data structure like this:

"posts" : {
"-KcOa8GXBl08V-WXeX8P" : {
  "author" : "@giovanny.piñeros",
  "hashtags" : [ "Hello", "World" ],
  "text" : "Hola Mundo!!!!!",
  "timestamp" : 5.08180914309278E8,
  "title" : "Hello World",
  "userid" : "hmIWbmQhfgh93"
}

As you can see i've added a timestamp attribute, when i query my data with a child added event, i pass that data to a post object, then i append that object to an array of posts from which my table view will feed:

 self.posts.append(post)
 self.posts.sort(by: {$0.timestamp! > $1.timestamp!})
 self.tableViewFeed.reloadData()

With the sort method i've managed to order my data in the desire order of posts, from the newest to the oldest. I Hope this approach could help anyone :).