Swift/Firebase - Sort Posts in Tableview by Date

Swift/Firebase - Sort posts in tableview by date

The question is little bit unclear but if you want to populate a tableView you will need to get the data from Firebase, populate an array (the dataSource), and reload the tableView.

First off we need to set up an .ChildAdded observer to load the data into the messagesArray, which is used as the tableView datasource

class AppDelegate: NSObject, NSApplicationDelegate {

var messagesArray = [[String:String]]() //an array of dicts, tableView datasource
var initialLoad = true

and the code to load the data initially and then watch for added messages

messagesRef.observeEventType(.ChildAdded, withBlock: { snapshot in

let dict = [String: String]()
dict["date"] = snapshot.value("date")
dict["msg"] = snapshot.value("message")
dict["sender"] = snapshot.value("sender")

self.messagesArray.append(dict)

if ( self.initialLoad == false ) { //upon first load, don't reload the tableView until all children are loaded
self.itemsTableView.reloadData()
}
})

then - and this is the cool part...

    //this .Value event will fire AFTER the child added events to reload the tableView
// the first time and to set subsequent childAdded events to load after each child
// is added in the future
messagesRef.observeSingleEventOfType(.Value, withBlock: { snapshot in

print("inital data loaded so reload tableView! \(snapshot.childrenCount)")
self.messagesTableView.reloadData()
self.initialLoad = false
})

With the above code, the order of the data is by their key - and if those keys were generated by Firebase, they will be sequential in the order the messages were created.

Your tableView is then populated from the messagesArray, and you can pick off the date, message and sender to put into the tableView columns or however you want the populate your cellView.

Your other requirement was to have them ordered by Date, descending. That's a super great question and had has answer here - it was a two part question but you'll see the thought process.

In Firebase, how can I query the most recent 10 child nodes?

you will also want to leverage Firebases query capabilities to get the specific data you want instead of the general data from above...

messagesRef.queryOrderedByChild("date").observeEventType(.ChildAdded, withBlock: {
snapshot in
//create a dict from the snapshot and add to tableview
})

How to sort data in Firebase?

There's a lot of code in the question and sometimes, simpler is better. So let's take a Post class, load the posts, get the associated user name and store it in an array. Then when complete, sort and print the posts in reverse chronological order.

A class to hold the post data and the user name

class PostClass {
var post = ""
var timestamp: Int! //using an int for simplicity in this answer
var user_name = ""

init(aPost: String, aUserName: String, aTimestamp: Int) {
self.post = aPost
self.user_name = aUserName
self.timestamp = aTimestamp
}
}

Note that if we want to have have both post data and user data we could do this

class PostUserClass {
var post: PostClass()
var user: UserClass()
}

but we're keeping it simple for this answer.

Then an array to store the posts

var postArray = [PostClass]()

and finally the code to load in all of the posts, get the associated user name (or user object in a full example).

let postsRef = self.ref.child("posts")
let usersRef = self.ref.child("users")
postsRef.observeSingleEvent(of: .value, with: { snapshot in
let lastSnapIndex = snapshot.childrenCount
var index = 0
for child in snapshot.children {
let childSnap = child as! DataSnapshot
let uid = childSnap.childSnapshot(forPath: "uid").value as! String
let post = childSnap.childSnapshot(forPath: "post").value as! String
let timestamp = childSnap.childSnapshot(forPath: "timestamp").value as! Int
let thisUserRef = usersRef.child(uid)

thisUserRef.observeSingleEvent(of: .value, with: { userSnap in
index += 1
//for simplicity, I am grabbing only the user name from the user
// data. You could just as easily create a user object and
// populate it with user data and store that in PostClass
// that would tie a user to a post as in the PostUserClass shown above
let userName = userSnap.childSnapshot(forPath: "Name").value as! String
let aPost = PostClass(aPost: post, aUserName: userName, aTimestamp: timestamp)
self.postArray.append(aPost) //or use self.postUserArray to store
// PostUserClass objects in an array.
if index == lastSnapIndex {
self.sortArrayAndDisplay() //or reload your tableView
}
})
}
})

and then the little function to sort and print to console

func sortArrayAndDisplay() {
self.postArray.sort(by: {$0.timestamp > $1.timestamp})

for post in postArray {
print(post.user_name, post.post, post.timestamp)
}
}

Note that Firebase is asynchronous so before sorting/printing we need to know we are done loading in all of the data. This is handled via the lastSnapIndex and index. The index is only incremented once each user is loaded and when all of the posts and users have been loaded we then sort and print as the data is complete.

This example avoids messy callbacks and completion handlers which may be contributing to the issue in the question - this piece of code is suspect and probably should be avoided due to the asynchronous nature of Firebase; the sort function is going to be called well before all of the users are loaded.

UserApi.shared.observeUserToPost(uid: userUid) { (user) in
self.postUser.append(user)
}
self.postUser.sort(by: {$0.postDate! > $1.postDate!})

*please add error checking.

How do you properly order data from Firebase chronologically

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

Firebase query sort order in swift?

When Firebase loads the data into your tableView data source array, call this:

yourDataArray.sortInPlace({$0.date > $1.date})

Swift 3 Version:

yourDataArray.sort({$0.date > $1.date})

Swift 4 Version:

yourDataArray.sort(by: {$0.date > $1.date})

Organize firebase data by timestamp

Here's how you sort an array of objects in place; acending is the default order.

Assuming you have a PostClass like this

class PostClass {
var timeStampDate = "" //assume the timestamps are yyyyMMddhhmmss strings
var post = ""
}

and they are stored in a posts array

var posts = [PostClass]()

the code to sort the posts object within the array is:

posts.sort(by: {$0.timeStampDate < $1.timeStampDate })

that being said, you may want to consider letting Firebase do the heavy lifting and ordering the data for you so it's presented in the snapshot in the correct order.

Suppose you have a Firebase structure

posts
post_0
post: "A post about some stuff"
timestamp: "2"
post_1
post: "Posting about how to post"
timestamp: "3"
post_2
post: "The post for you"
timestamp: "1"

and we want to load our posts, ordering by the timestamp

let postsRef = self.ref.child("posts")
let postQuery = postsRef.queryOrdered(byChild: "timestamp")
postQuery.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let post = dict["post"] as! String
let ts = dict["timestamp"] as! Int
print(ts, post)
}
})

and the output

1 The post for you
2 Posting about how to post
3 A post about some stuff

For this example, I used 1, 2, 3 as my timestamp but yyyyMMddhhmmss (20180618101000) format would work as well and provide human readable, proper ordering.

Grab Data, Sort Data, then Load into Table View (Swift 3 : Firebase)

Not sure if this is the best way to handle this, but you could add a counter that is incremented and then execute your sort and reload code once that counter is equal to the count of the following array.

var counter = 0

let databaseRef = FIRDatabase.database().reference()
for follower in following {
databaseRef.child("Posts").child(follower).observe(.value, with: {
DataSnapshot in
//Parse All The Data...

counter += 1

self.posts.insert(...)

if counter == following.count {
self.sortPosts()
}
}
}

func sortPosts() {
self.posts.sort{$0.date.compare($1.date) == .orderedDescending}

print("Test")

self.tableView.reloadData()
}

How to Sort TableViewCells by date string

Use sort instead of sorted. The sorted method returns a new sorted array, on the other hand, the sort method sorts the array on which it was called.

self.texttt.sort(by: { $0.timestampName < $1.timestampName })

This should also work, using sorted:

self.texttt = self.texttt.sorted(by: { $0.timestampName < $1.timestampName })


Related Topics



Leave a reply



Submit