kerbelda kerbelda - 2 months ago 11
Swift Question

How do you properly order data from Firebase chronologically

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)
})
}

Answer

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 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.

let date = NSDate()
// : "May 10, 2016, 8:55 PM" - Local Date Time
var formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
let defaultTimeZoneStr = formatter.stringFromDate(date)
// : "2016-05-10 20:55:06 +0300" - Local (GMT +3)
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
let utcTimeZoneStr = formatter.stringFromDate(date)
// : "2016-05-10 17:55:06 +0000" - UTC Time

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

Now, you can save this utcTimeZoneStr string on your Firebase posts as child like this,

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

I will retrieve five by five post, so I'm doing queryLimitedToLast(5) after queryOrderedByChild("timestamp") sorting all of my posts and reverse() to show user newest to oldest. Call initObserver() function in your viewDidLoad():

func initObserver() {

let query = DataService.ds.REF_POSTS.queryOrderedByChild("timestamp")
query.queryLimitedToLast(5).observeSingleEventOfType(.Value, withBlock: { snapshot in

    if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {

        self.posts = []
        for snap in snapshots {
            if let postDict = snap.value as? Dictionary<String, AnyObject> {

                let post = Post(postKey: snap.key, dictionary: postDict)
                self.posts.append(post)
            }
        }

        self.posts = self.posts.reverse()
        self.tableView.reloadData()
    }

})

}

If user scrolled down, continue retrieving first five post using like LimitedToLast(UInt(5+5+..)). Every scrolledDown call another function to handle this case loadMore()

// scrolled down
override func scrollViewDidScroll(scrollView: UIScrollView) {

    if scrollView.contentOffset.y >= scrollView.contentSize.height - self.view.frame.size.height * 2 {
        print("Called didScroll")
        self.loadMore()
    }
}

And then finally you can use this loadMore() func, I prevent retrieve same data twice, using another array to keep 5 old posts. Before merge process, you can convert tableView's actual array with reverse() and append() all of them after this old 5 posts. When you reverse() again and reload tableView you will see oldest datas loaded while scrolling smoothly.

func loadMore() {

if self.page < self.postCounter {

    let newPage = page + 5
    self.page = newPage

    let modHandle = self.page - self.postCounter!
    if  modHandle > 0 && modHandle < 5 {
        self.isModularPage = true
        self.modPage = 5 - modHandle
    }

    let query = DataService.ds.REF_POSTS.queryOrderedByChild("timestamp")
    query.queryLimitedToLast(UInt(self.page)).observeSingleEventOfType(.Value, withBlock: { snapshot in

        if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {

            self.postsAddFirst.removeAll(keepCapacity: false)
            for snap in snapshots {
                if let postDict = snap.value as? Dictionary<String, AnyObject> {

                    let post = Post(postKey: snap.key, dictionary: postDict)

                    self.postsAddFirst.append(post)

                    if self.isModularPage {
                        self.modPage -= 1
                        if self.modPage == 0 {
                            self.isModularPage = false
                            break
                        }
                    }
                    else {

                        if self.i == 4 {
                            self.i = 0
                            break 
                        }
                        self.i += 1
                    }
                }
            }

            self.posts = self.posts.reverse()
            for post in self.posts {
                self.postsAddFirst.append(post)
            }

            self.posts.removeAll(keepCapacity: false)
            self.posts = self.postsAddFirst

            self.postsAddFirst.removeAll(keepCapacity: false)
            self.posts = self.posts.reverse()

            for post in self.posts {
                print(post.postKey)
            }

            self.tableView.reloadData()

        }
    })
}
}

Notes: You need to take total post count to postCounter, using an observer with childCounts and you need to define new array to provide my above code. (by the way assign page 5 while starting)

In addition to show user logic 5s. 4w. like Instagram post shared information, you need to find time range between before saved above UTC date time and now UTC date time. And then you can use NSCalendarUnit components like below:

// Takes your UTC time and then convert it as Local Time.
// think that you got this from firebase any post child:
let dateStringUTC = "2016-01-05 13:49:48 +0000" 
let formatToLocal = NSDateFormatter()
formatToLocal.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"

if let dateFromString = formatToLocal.dateFromString(dateStringUTC) {
    formatToLocal.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let localStringFromDate = formatToLocal.stringFromDate(dateFromString)
    // : "2016-05-06 15:28:44" // Converted Local Time

// Get A Local Time and then compare it Current Local Time.
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

let from = formatter.dateFromString(localStringFromDate)
// : "Apr 5, 2015, 4:49 PM"
let now = NSDate()
// : "May 6, 2016, 4:41 PM"
let components: NSCalendarUnit = [.Second, .Minute, .Hour, .Day, .WeekOfMonth]
let difference = NSCalendar.currentCalendar().components(components, fromDate: from!, toDate: now, options: [])

// logic to show information:
if difference.second <= 0 {
    "now"
}
if difference.second > 0 && difference.minute == 0 {
    "\(difference.second)s."
}
if difference.minute > 0 && difference.hour == 0 {
    "\(difference.minute)m."
}
if difference.hour > 0 && difference.day == 0 {
    "\(difference.hour)h."
}
if difference.day > 0 && difference.weekOfMonth == 0 {
    "\(difference.day)d."
}
if difference.weekOfMonth > 0 {
    "\(difference.weekOfMonth)w."
    // "56w."

    if Float(difference.weekOfMonth) > 52.17 {
        let number = Float(difference.weekOfMonth) / 52.17
        // : 1.076923 // Float
        let strYear = String(format:"%.1f", number)
        // : "1.1"
        "\(strYear) year"
    }
}
}

If this helped you, you can support me :)

Comments