Firebase query sort order in swift? - ios

When I load the following Firebase Database data into my tableView, the data is sorted in ascending order by date. How can I order this by descending (show the newest post at the top)?
Query in Xcode:
let ref = self.rootRef.child("posts").queryOrderedByChild("date").observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
JSON export:
"posts" : {
"-KMFYKt7rmfZINetx1hF" : {
"date" : "07/09/16 12:46 PM",
"postedBy" : "sJUCytVIWmX7CgmrypqNai8vGBg2",
"status" : "test"
},
"-KMFYZeJmgvmnqZ4OhT_" : {
"date" : "07/09/16 12:47 PM",
"postedBy" : "sJUCytVIWmX7CgmrypqNai8vGBg2",
"status" : "test"
},
Thanks!!
EDIT: Below code is the entire solution thanks to Bawpotter
Updated query:
let ref = self.rootRef.child("posts").queryOrderedByChild("date").observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
let post = Post.init(key: snapshot.key, date: snapshot.value!["date"] as! String, postedBy: snapshot.value!["postedBy"] as! String, status: snapshot.value!["status"] as! String)
self.posts.append(post)
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: self.posts.count-1, inSection: 0)], withRowAnimation: .Automatic)
tableView cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell", forIndexPath: indexPath) as! PostCell
self.posts.sortInPlace({$0.date > $1.date})
self.tableView.reloadData()
Post.swift:
import UIKit
class Post {
var key: String
var date: String
var postedBy: String
var status: String
init(key: String, date: String, postedBy: String, status: String){
self.key = key
self.date = date
self.postedBy = postedBy
self.status = status
}
}

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

While I do recommend doing what was posted and creating a Class and doing it that way, I will give you another way to do sort it.
Since you already have it sorted in Ascending order from Firebase and you know the amount of records, you can do this:
guard let value = snapshot.children.allObjects as? [FIRDataSnapshot] else {
return
}
var valueSorted: [FIRDataSnapshot] = [FIRDataSnapshot]()
var i: Int = value.count
while i > 0 {
i = i - 1
valueSorted.append(value[i])
}

Simply use .reverse() before using data
Example:
myRef.observe(.value) { (snapshot) in
guard var objects = snapshot.children.allObjects as? [DataSnapshot] else {
return
}
objects.reverse() //<========= HERE
//Now use your `objects`
...
}

Swift 5:
Add reversed() to your objects after sorting by the required field.
For example, let's assume you have a day of the month in the "day" field in FireStore.
Something like this will do the trick (call loadData() function in viewDidLoad to see the output):
let db = Firestore.firestore()
func loadData() {
db.collection("FireStoreCollectionName").order(by: "day").getDocuments { (querySnapshot, error) in
if let e = error {
print("There was an issue retrieving data from Firestore, \(e)")
} else {
for document in querySnapshot!.documents.reversed() {
let data = document.data()
let fDay = data["day"] as! Int
print(fDay)
}
}
}
}

Related

Firebase Realtime database - tableview update all the cell data if new data added

I am working on a group chat app and trying to fetch the last message from a node to display the last message under the group name. Everything is working fine. When the group receives a new message, the last message is showing in the correct group and some other random groups as well. If I open the correct group and come back, all the other groups are showing the correct last message. Kindly help me with the below code.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groupList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:GroupsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "GroupsTableViewCell") as! GroupsTableViewCell
cell.selectionStyle = .none
var user: Groupslist
if groupList.count > 0 {
user = groupList[indexPath.row]
cell.lbl_name_group.text = user.name;
cell.view_count.isHidden = true
GetLastMsg(groupID: user.group_id ?? "", cell: cell)
cell.img_group.image = UIImage.init(named:"final_grp")
GetUnReadMsgCount(groupID: user.group_id ?? "", cell: cell)
}
return cell
}
groupList contains the list of groups which is fetched from firebase on viewWillAppear.
func GetLastMsg(groupID : String, cell : GroupsTableViewCell){
let userRef2 = rootRef2.child("message").child(groupID).queryLimited(toLast: 1)
userRef2.observe( .value, with: { snapshot in
guard let _ = snapshot.value as? [String:AnyObject] else {
print("Error")
return
}
print("children count: \(snapshot.children.allObjects.count)")
for snap in snapshot.children.allObjects as! [DataSnapshot] {
let message = Message()
let value = snap.value as? [String: Any] ?? [:]
let text = value["text"] as? String ?? "Name not found"
let mimType = value["mimType"] as? String
let name_doc = value["name_doc"] as? String ?? "file.file"
message.text = text
message.mimType = mimType
message.name_doc = name_doc
message.timestamp_group = (value["timestamp"] as! NSNumber)
print(value["idSender"] as? String)
print(message.name_doc)
var msg_last = ""
// cell.lbl_lastmsg.text = msg_last
if message.mimType == "image/jpeg" {
msg_last = "Image"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "im")
}else if message.mimType == "audio/3gpp" {
msg_last = "Audio"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "mi")
}else if message.mimType == "video/mp4" {
msg_last = "Video"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "vi")
}else if message.mimType == "application/pdf" {
let name_docs = message.name_doc!.split{$0 == "."}.map(String.init)
msg_last = name_docs.last!
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "doc")
}else if mimType == nil{
msg_last = message.text!
cell.img_small_width.constant=0
}
DispatchQueue.main.async {
cell.lbl_lastmsg.text = msg_last
}
}
}
})
}
New method:
func GetUnReadMsgCount(groupID : String, cell : GroupsTableViewCell) {
rootRef2.child("group_message_unread").child(current_user!).child(groupID).observe(.childAdded, with: { (snapshot) -> Void in
print(snapshot.childrenCount)
print(snapshot.children.description)
if snapshot.childrenCount > 0 {
DispatchQueue.main.async {
cell.view_count.isHidden = false
cell.lbl_count.text = String(snapshot.childrenCount)
// self.tbl_groups.reloadRows(at: [IndexPath(row: 3, section: 0)], with: .fade)
// self.tbl_groups.reloadData()
}
}
})
}
That's the expected behavior. In a .observe( .value listener, the snapshot is always the complete data at the path. So when you add a child node, the snapshot fires with the entire new data at userRef2.
You have two main options to not get duplicates in your UI:
Clear all messages when there is an update, before adding the data to the table again. So that'd be right before for snap in snapshot.children in your code. This is by far the simplest way to solve the problem if duplicate rows, but may lead to some flashing in your UI.
Listen for child events, instead of observing the entire value. With this you'll get a single .childAdded event for the new child, you can get rid of the for snap in snapshot.children.allObjects and just add the singular new child node to the table.

How do I use Firebase Database to display multiple routes/childs in Table View using Xcode/Swift

Here is my Firebase Database:
And here's my code:
class AdminSearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var ref: DatabaseReference!
var jobList = [jobModel2]()
#IBOutlet weak var tlb2: UITableView!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell2", for: indexPath) as! ViewController2TableViewCell
let job: jobModel2
job = jobList[indexPath.row]
cell.ship.text = job.consignee
cell.reference.text = job.reference
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jobList.count
}
override func viewDidLoad() {
super.viewDidLoad()
if FirebaseApp.app() == nil {
FirebaseApp.configure()
}
Database.database().reference().child("jobs").observe(DataEventType.value) { (snapshot) in
if snapshot.childrenCount>0 {
self.jobList.removeAll()
for jobs in snapshot.children.allObjects as! [DataSnapshot]{
let jobObject = jobs.value as? [String: AnyObject]
let jobConsignee = jobObject?["consignee"]
let jobReference = jobObject?["reference"]
let job = jobModel2( consignee: jobConsignee as! String?,
reference: jobReference as! String?
)
self.jobList.append(job)
}
self.tlb2.reloadData()
}
}
}
}
The problem I am facing is that their is nothing coming up in my TableView, I think its to do with the .childs, as I think I need to change it from just "jobs" to something but I can not figure it out.
I want to display the list of all the references from all of the UID's not just one.
CODE THAT DISPLAYS IN PRINT:
func readJobs() {
let ref = Database.database().reference()
let ref1 = ref.child("jobs").queryOrderedByKey()
ref1.observeSingleEvent(of: .value, with: { snapshot in
let allUsersAndJobs = snapshot.children.allObjects as! [DataSnapshot]
for user in allUsersAndJobs {
let uid = user.key
print("user id: \(uid)")
let thisUsersJobs = user.children.allObjects as! [DataSnapshot]
for job in thisUsersJobs {
let jobKey = job.key
let consignee = job.childSnapshot(forPath: "consignee").value as! String
print(" job #: \(jobKey)")
print(" consignee: \(consignee)")
}
}
})
}
So that's not going to work - the firebase structure doesn't match what you're trying to read in.
Your structure is
jobs
uid_0
job_key_0
...
job_key_1
...
uid_1
job_key_0
...
and you're reading the jobs node which contains the direct children but you're not reading the children of the children.
uid_0
uid_1
etc
So the Firebase structure is deeper than what you're reading so you need go read one level further down in code to get to the job child nodes.
Assumeing 5hs... and 7py... are user id's here the code to read each node, print the user uid and then their jobs
func readJobs() {
let ref = self.ref.child("jobs")
ref.observeSingleEvent(of: .value, with: { snapshot in
let allUsersAndJobs = snapshot.children.allObjects as! [DataSnapshot]
for user in allUsersAndJobs {
let uid = user.key
print("user id: \(uid)")
let thisUsersJobs = user.children.allObjects as! [DataSnapshot]
for job in thisUsersJobs {
let jobKey = job.key
let consignee = job.childSnapshot(forPath: "consignee").value as! String
print(" job #: \(jobKey)")
print(" consignee: \(consignee)")
}
}
})
}
and the output
user id: 5HS
job #: job_0
consignee: Anni
job #: job_1
consignee: Ralph
user id: 7py
job #: job_0
consignee: Larry
job #: job_1
consignee: Bill

Delete cell from UITableView and Firebase

I try to delete cell (left swipe) and remove data from Firebase Database and Storage. Using this code:
struct Records {
let title: String
let source: String
var length: String
let recordUrl: String
let username: String
init(dictionary: [String: Any]) {
self.title = dictionary["title"] as? String ?? ""
self.source = dictionary["source"] as? String ?? ""
self.length = dictionary["length"] as? String ?? ""
self.recordUrl = dictionary["recordUrl"] as? String ?? ""
self.username = dictionary["username"] as? String ?? ""
}
}
I've got a var
var records = [Records]()
Trying to delete cell
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
guard let uid = Auth.auth().currentUser?.uid else { return }
if (editingStyle == .delete) {
records.remove(at: indexPath.item)
tableView.deleteRows(at: [indexPath], with: .fade)
Database.database().reference().child("users").child(uid).child("records").removeValue()
}
}
Firebase JSON enter image description here
I need to delete item inside "records"(appears as cell in my TableView), but now using my code I delete whole "records"
You need to save the ID for the records too. This way you can create a path to that particular record. After that just add one more child in the path.
Like this.
Database.database().reference().child("users").child(uid).child("records").child(record.id).removeValue()
Got some new. Can get key of all items in branch "records"
guard let uid = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("users").child(uid).child("records")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any] else { return }
let record = self.records[indexPath.item]
dictionaries.forEach({ (key, value) in
print("Key \(key)")
but still can't get an id to delete of current item (record in branch "records")

Attempting to get numberOfRowsInSection count to be equal to an array count from another file in Swift

Currently I am fetching data from firebase, turning the data into an array of objects and trying to have that array count be the count of the "numberOfRowsInSection".
So, I get the right count on the view controller where the array is declare but I keep getting 0's when I pass that data to the tableview controller.
Below I am showing the viewcontroller where I am fetching data and putting it in a filtered array:
func fetchAllData( completion: #escaping (_ call: [Restaurant]) -> Void) {
self.ref = Database.database().reference()
self.ref?.observe(.value, with: { (snap) in
guard let topArray = snap.value as? [[String:Any]] else {print(":(") ; return }
var restaurantArray = [Restaurant]()
for dictionary in topArray {
self.restaurantGroup.enter()
guard let address = dictionary["Address"] as? String,
let city = dictionary["City"] as? String,
let inspectionDate = dictionary["Inspection Date"] as? String,
let name = dictionary["Name"] as? String,
let major = dictionary["Number of Occurrences (Critical Violations)"] as? Int,
let minor = dictionary["Number of Occurrences (Noncritical Violations)"] as? Int,
let violationTitle = dictionary["Violation Title"] as? String else { continue }
print(2)
//MARK: - creates restaurants from the list above
let restaurant = Restaurant(address: address, city: city, inspectionDate: inspectionDate, name: name, major: major, minor: minor, violationTitle: violationTitle)
print(3)
//MARK: - Adds a restaurant to restaurant array instance
restaurantArray.append(restaurant)
}
self.restaurantList = restaurantArray
self.restaurantGroup.leave()
completion(self.restaurantList)
let dictionaryNew = Dictionary(grouping: self.restaurantList) { $0.name + " " + $0.address}
self.restaurantListModified = dictionaryNew
print(self.restaurantListModified.count)
})
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
var filteredArray = [[Restaurant]]()
guard let userSearch = searchBar.text?.uppercased() else { return }
pulleyViewController?.setDrawerPosition(position: .partiallyRevealed, animated: true)
var nameArray = [String]()
for (key, value) in restaurantListModified {
if value[0].name.hasPrefix(userSearch.uppercased()){
filteredArray.append(value)
}
}
for subarray in filteredArray {
let nameArrayForTBView = subarray[0].name
nameArray.append(nameArrayForTBView)
}
self.tableViewNameArray = nameArray
print("\(tableViewNameArray.count)😋😋😋😋😋😋😋😋😋😋😋😋")
print("this First")
}
}
The tableViewNameArray is the array I am trying to pass to this tableView Controller
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("This second")
print(MapViewController.sharedMapViewController.tableViewNameArray.count)
return MapViewController.sharedMapViewController.tableViewNameArray.count
}
I also have my array set to a Notification Center that is received in the tableView Controller as shown below:
var tableViewNameArray = [String]() {
didSet {
DispatchQueue.main.async {
DispatchQueue.main.async {
NotificationCenter.default.post(name: MapViewController.RestaurantNotification.notificationSet, object: self)
}
}
}
}
Points:
My array from viewcontroller is getting the right count on the view controller but the accurate count is not being passed to the tableView Controller.
the tableview count for the array being passed is 0
I found the problem that #Kamran pointed to. I was not assigning self.tableViewNameArray = nameArray. So I changed
var tableViewNameArray to
static var tableViewNameArray
If you're new to programming like I, make sure you read a bit more on local and global variables. Very helpful.

Firebase - sort posts by number of keys and show in TableView

I have made app that save users message and its username to Firebase with childByAutoID key. Inside there are childs that saves Username, message, likes and PostID (as you can see on image below).
After a lot of research and a lot of trying writing code by myself, I figure out that likes need to be saved as autoID keys inside separate child and then you have to count that keys to get number of likes (you'll see that child also on image under and that child is named "Liked")
Everything is displayed in tableView cell.
But all of them are displayed randomly which is OK (I would prefer to be ordered by date added), but what would really like is that loaded data in next next VC to be displayed as:
TOP 10 of the week
TOP 10 of the month
ALL BEST etc...
There'll be separate button for menu and when you press it, you'll be presented with next VC that contain Table View with same data, but this time sorted by most liked post.
This is code that writes keys to LIKED child path (when like is pressed on already loaded data from Firebase database):
#IBAction func likePressed(_ sender: Any) {
let ref = Database.database().reference()
self.likeButton.isEnabled = false
let key = ref.child("Frusters").childByAutoId().key
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
let updateLikes = ["Liked/\(key)" : key] as [String : Any]
ref.child("Frusters").child(self.postID).updateChildValues(updateLikes, withCompletionBlock: { (error, reff) in
if error == nil {
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snap) in
if let properties = snap.value as? [String : AnyObject] {
if let likes = properties["Liked"] as? [String : AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
let update = ["likes" : count]
ref.child("Frusters").child(self.postID).updateChildValues(update)
self.likeButton.isHidden = true
self.unlikeButton.isHidden = false
self.likeButton.isEnabled = true
}
}
})
}
})
})
ref.removeAllObservers()
}
and this is the code that loads data and put it in my table view:
func loadData() {
self.fetchPosts.removeAll()
let ref = Database.database().reference()
ref.child("Frusters").observeSingleEvent(of: .value, with: { (snapshot) in
if let postDict = snapshot.value as? [String:AnyObject] {
for (_,postElement) in postDict {
print(postElement);
let post = Post()
post.username = postElement["Username"] as? String
post.message = postElement["Message"] as? String
post.likes = postElement["likes"] as? Int
post.postID = postElement["postID"] as? String
self.fetchPosts.append(post)
}
}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
ref.removeAllObservers()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.fetchPosts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! PostTableViewCell
cell.messageLabel.text = self.fetchPosts[indexPath.row].message
cell.usernameLabel.text = self.fetchPosts[indexPath.row].username
cell.likeLabel.text = "\(self.fetchPosts[indexPath.row].likes!) Likes"
cell.postID = self.fetchPosts[indexPath.row].postID
cell.bckgView.layer.cornerRadius = 0
cell.bckgView.layer.shadowOffset = CGSize(width: 0, height: 1)
cell.bckgView.layer.masksToBounds = false
cell.bckgView.layer.shadowColor = UIColor.black.cgColor
cell.bckgView.layer.shadowOpacity = 0.3
cell.bckgView.layer.shadowRadius = 4
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
Well, problem is that I do not know how to insert inside my new UITableView top 10 post with most likes, to sort them from most liked post to next 9 of them with most likes.
Also, is it possible to sort them most liked this month or this week?
Does this keys that Firebase database makes (by autoID) contain date of created post or do I have to insert new child with date inside and then in code combine "date" child and "liked" child to be presented as top 10 liked post between 1st and last of this month?
Thanks in advance. ;)
1-You don't have to store each like separately, if all you care about is the number. You can just update the number.
#IBAction func likePressed(_ sender: Any) {
let ref = Database.database().reference()
self.likeButton.isEnabled = false
let key = ref.child("Frusters").childByAutoId().key
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
let counted = snapshot.value as? Int
self.ref.child("Flusters".child(self.postID).child("likes").setValue(counted! + 1)
})
2- Yes, you can sort by likes. you'd need to use the .queryOrdered function. Update the code as follows
func loadData() {
self.fetchPosts.removeAll()
let ref = Database.database().reference()
ref.child("Frusters").queryOrdered(byChild: "likes").observeSingleEvent(of: .value, with: { (snapshot) in
if let postDict = snapshot.value as? [String:AnyObject] {
for (_,postElement) in postDict {
print(postElement);
let post = Post()
post.username = postElement["Username"] as? String
post.message = postElement["Message"] as? String
post.likes = postElement["likes"] as? Int
post.postID = postElement["postID"] as? String
self.fetchPosts.append(post)
}
}
self.tableView.reloadData()
3- To order by top week, month, you'd have to keep track of a timestamp.

Resources