Order tableviewcell by child timestamp - swift

I tried with this code to sort my posts by timestamp it doesn't work, each time I launch the simulator the order of the cells is different, I suppose this isn't the way to do it, could somebody explain me where I am wrong...
I edited the code, now my problem is that the most recent posts are displayed at the bottom and I would like them to to be displayed at the top
self.user.removeAll()
for child in DataSnapshot.children.allObjects as! [DataSnapshot] {
print("Processing user \(child.key)")
let value = child.value as? NSDictionary
//if country == "UNITED STATES"{
if let uid = value?["userID"] as? String{
if uid != Auth.auth().currentUser!.uid {
//
let userToShow = User()
if let fullName = value?["username"] as? String , let imagePath = value?["photoURL"] as? String{
userToShow.username = fullName
userToShow.imagePath = imagePath
userToShow.userID = uid
self.user.append(userToShow)
}
}
}
}

As soon as you call DataSnapshot.value, you're converting the data in the snapshot into a dictionary. And the order if keys in that dictionary is not guaranteed.
To maintain the order of the elements as they come back from the database, you need to loop over DataSnapshot.children. See these questions for examples of how to do that:
Iterate over snapshot children in Firebase
post on the firebase-talk mailing list
For your code this would look something like:
ref.child("users").queryOrdered(byChild: "timestamp").observeSingleEvent(of: .value, with: { snapshot in
self.user.removeAll()
for child in snapshot.children.allObjects as [DataSnapshot] {
print("Processing user \(child.key)")
let value = child.value as? NSDictionary
if let uid = value["userID"] as? String {
...
}
}
self.tableview.reloadData()
})

Related

Not able to read data from Firebase realtime database

I have stored the comments under a post in firebase realtime database. The problem i have is that when i try to parse out the data from firebase i get an error that says Unexpectedly found nil while unwrapping an Optional value. So for example if i try to pront the data stored under degree, i get this nil error. But when i print "comments" instead of the "degree" i successfully fetch the data. My database structure looks like this.
func obeserveComments() {
// get auto-id of post
let commentKey = self.keyFound
let postRef = Database.database().reference().child("posts").child(commentKey)
var tempComments = [Comments]()
postRef.observe(.value, with: {(snapshot) in
if let dict = snapshot.value as? [String:Any] {
if let comments = dict["comments"] as? [String:Any] {
let degree = comments["reply degree"] as! String
// let name = comments["reply name"] as! String
// let text = comments["reply text"] as! String
// let university = comments["reply university"] as! String
// let photoURL = comments["reply url"] as! String
// let url = URL(string: photoURL)
// let timestamp = comments["timestamp"] as! Double
print(degree)
}
}
})
}
The answer by #aytroncb is a good answer, I prefer to leave Firebase data 'Firebasy' as long as possible. In other words coverting to dictionaries looses ordering and and find code like this
[String: [String: [String: Any]]]
To be very hard to read.
I prefer
let snap = snapshot.childSnapshot("comments") //snap becomes a DataSnapshot
So my solution maintains the order and leverages .childSnapshot to leave data in it's DataSnapshot form.
func readPostComments() {
let postRef = self.ref.child("posts") //self.ref points to my firebase
postRef.observeSingleEvent(of: .value, with: { snapshot in
let allPosts = snapshot.children.allObjects as! [DataSnapshot]
for postSnap in allPosts {
print("postId: \(postSnap.key)")
let commentsSnap = postSnap.childSnapshot(forPath: "comments") //will be a DataSnapshot
let allComments = commentsSnap.children.allObjects as! [DataSnapshot]
for commentSnap in allComments {
print(" commentId: \(commentSnap.key)")
let replyDegree = commentSnap.childSnapshot(forPath: "reply_degree").value as? String ?? "No Degree"
let replyName = commentSnap.childSnapshot(forPath: "reply_name").value as? String ?? "No Name"
print(" degree: \(replyDegree) by: \(replyName)")
}
}
})
}
EDIT
For a single post, remove the top part of the code that reads in and iterates over all posts.
func readCommentsForOnePost() {
let postRef = self.ref.child("posts")
let postCommentRef = postRef.child("post_0")
postCommentRef.observeSingleEvent(of: .value, with: { snapshot in
print("postId: \(snapshot.key)")
let commentsSnap = snapshot.childSnapshot(forPath: "comments") //will be a DataSnapshot
let allComments = commentsSnap.children.allObjects as! [DataSnapshot]
for commentSnap in allComments {
print(" commentId: \(commentSnap.key)")
let replyDegree = commentSnap.childSnapshot(forPath: "reply_degree").value as? String ?? "No Degree"
let replyName = commentSnap.childSnapshot(forPath: "reply_name").value as? String ?? "No Name"
print(" degree: \(replyDegree) by: \(replyName)")
}
})
}
Its because firebase is returning your data like this
{
"MAKFW244kdL)Cw;1": [Array of data],
"LOPSw!35pa3flAL4": [Array of data],
"ALV34VR4_A6Vn1a": [Array of data]
}
So change your initial casting of snapshot.value to this:
if let dict = snapshot.value as? [String: [String: Any]]
then loop through that new dictionary like this:
for objectJson in dict.values {
if let comments = objectJson["comments"] as? [String: [String: Any]] {
for commentJson in comments.values {
let degree = commentJson["reply_degree"] as? String
}
}
}
Update
Just read through your post again and noticed your trying to access the comments directly with a key, your first going to need to provide the PostId. Then you can use the above code to loop through the objects
let postRef = Database.database().reference().child("posts").child(postID)
alternatively I believe you can have the comments returned as a normal list by doing something like this:
let postRef = Database.database().reference().child("posts").child("\(postID)/{id}")

retrieving data from auto id database child

I'm kinda new on swift programming, my case is:
I have a database on firebase realtime "see picture", I want to retrieve the firstName and lastName data of a specific ID only.
for example:
When i'm at login screen and logged in using this email: "ali_y_k#hotmail.com", and when going to the next screen i want to display the firstName and lastName of this email in the text fields showing on picture
I have tried several solutions but the problem always was I can't enter the random IDs child to fetch the firstName and lastName
there is what i tried:
First
func retriveInfo () {
let databaseRef = Database.database().reference().child("User_Informations")
databaseRef.observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary<String,String>
let firstName = snapshotValue["firstName"]!
let lastName = snapshotValue["lastName"]!
let email = snapshotValue["email"]!
print(firstName,lastName,email)
}
}
This is printing all data (firstName,lastName,email) from every id
Second
func retriveInfo() {
let databaseRef = Database.database().reference().child("User_Informations")
databaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
for snap in snapshot.children {
let userSnap = snap as! DataSnapshot
let uid = userSnap.key //the uid of each user
let userDict = userSnap.value as! [String:AnyObject] //child data
let firstName = userDict["firstName"] as! String
let lastName = userDict["lastName"] as! String
print("key = \(uid) First Name = \(firstName), Last Name = \(lastName)")
}
})
This will print every key "Id" and all the info
Thank You in advance :)
Since you have childByAutoId you have to use query ordered and query equal.
let reference = Database.database().reference().child("User_Informations").queryOrdered(byChild: "email")
reference.queryEqual(toValue: "ali_y_k#hotmail.com").observeSingleEvent(of: .childAdded) { (snapshot) in
let dictionary = snapshot.value as! [String : Any]
let firstName = dictionary["firstName"]
print(firstName)
}
You need to use the current user id after you login
let currentUserUid = FIRAuth.auth()!.currentUser!.uid
let databaseRef = Database.database().reference().child("User_Informations/\(currentUserUid)")
databaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
}

Firebase don't send me my value into my variable

I've got a code which normally should return to me a value from Firebase.
My Firebase struct is :
Experience{
UserId{
LDG_DAY: "4"
LDG_NIGHT: "0"
APCH_IFR: "0"
}
}
My code is :
func getUserExp(){
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) {(snapshot) in
if snapshot.hasChild(userID!){
let value = snapshot.value as? NSDictionary
let ldg_day = value?["LDG_DAY"] as? String ?? "123"
let ldg_night = value?["LDG_NIGHT"] as? String ?? "0"
let apch_ifr = value?["APCH_IFR"] as? String ?? "0"
self.intLdgDay = Int(ldg_day)!
self.intLdgNight = Int(ldg_night)!
self.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
}
}
}
Now the code didn't work as I would like... In fact my code return the basic as? String ?? "123" value but the snapshot.value get the good value from firebase ...
What's wrong ? I use this code for many other part of my app and no problems about it ?
Thanks for your help
I believe you want to ensure the node exists before trying to read the child data.
NOTE:
I see the path to read has the uid commented out so it's unclear if you intended to read a single user (leaving in the uid) or if you actually wanted to load every user at one time (thousands). This answer assumes you are intending to read that specific user node only. See #Callam answer if you intended to read ALL of the users nodes at one time.
The code you have now is using snapshot.hasChild which looks within the node to see if the child, the users uid exists, and it doesn't so the code will always fail.
if snapshot.hasChild(userID!)
I think what you want to do is use snapshot.exists to ensure it's a valid node before reading. Here's the code:
let experienceRef = self.ref.child("Experience")
let usersExpRef = experienceRef.child(uid)
usersExpRef.observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as! [String: Any]
let ldg_day = value["LDG_DAY"] as? String ?? "123"
print("string = \(ldg_day)")
} else {
print("the \(uid) node does not exist")
}
}
I would also suggest safely unwrapping options before attempting to work with them as they could be nil, and that would crash your code.
guard let thisUser = Auth.auth().currentUser else { return }
let uid = thisUser.uid
Note I also replaced the old objc NSDictionary with it's Swifty counterpart [String: Any]
Assuming your struct is from the root, and Experience contains more than one user ID, your code is currently observing the value for all user IDs since the /*.child(userID!)*/ is commented out.
Therefore you are requesting every user's experience and checking on the client if the current user exists as a child – this will succeed if the current user's ID is present at Experience/$uid.
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.hasChild(userID!) {
let value = snapshot.value as? NSDictionary
Now we have a snapshot with all Experiences and we've confirmed that it has a child for the current user's ID – we would need to get that child and cast the value of that to a dictionary.
let value = snapshot.childSnapshot(forPath: userID).value as? NSDictionary
This fixes the issue but obviously, we don't want to download every experience on a single user's device, and they maybe shouldn't even have the permission to request that reference location either.
So if you uncomment .child(userID!), the snapshot will be of just one Experience, so snapshot.hasChild(userID!) will fail. Instead, you can use snapshot.exists() and/or a conditional cast to determine if the snapshot for the userID is existent and/or thereby castable.
func getUserExp() {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience").child(userID!).observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as? [String:String]
let ldg_day = value?["LDG_DAY"] ?? "123"
let ldg_night = value?["LDG_NIGHT"] ?? "0"
let apch_ifr = value?["APCH_IFR"] ?? "0"
self?.intLdgDay = Int(ldg_day)!
self?.intLdgNight = Int(ldg_night)!
self?.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \(snapshot.key) doesn't exist")
}
}
}
You can clean this up a bit with a struct and extension.
// Experience.swift
struct Experience {
var ldg_day: String
var ldg_night: String
var apch_ifr: String
}
extension Experience {
static var currentUserRef: DatabaseReference? {
return Auth.auth().currentUser.flatMap {
return Database.database().reference(withPath: "Experience/\($0.uid)")
}
}
init?(snapshot: DataSnapshot) {
guard snapshot.exists() else { return nil }
let value = snapshot.value as? [String:String]
self.ldg_day = value?["LDG_DAY"] ?? "123"
self.ldg_night = value?["LDG_NIGHT"] ?? "0"
self.apch_ifr = value?["APCH_IFR"] ?? "0"
}
}
Et voilà,
func getUserExp() {
Experience.currentUserRef?.observeSingleEvent(of: .value, with: { [weak self] in
if let experience = Experience(snapshot: $0) {
self?.intLdgDay = Int(experience.ldg_day)!
self?.intLdgNight = Int(experience.ldg_night)!
self?.intApchIfr = Int(experience.apch_ifr)!
print("string = \(experience.ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \($0.key) doesn't exist")
}
})
}

Getting values from Firebase snapshot in Swift

Im successfully getting data from Firebase but I can't manage to push it into array to use. My database is as follows:
users
-Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
-email : "mike#gmail.com"
-lists
-LJiezOzfDrqmd-hnoH-
-owner: Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
-LJif-UgPgbdGSHYgjY6
-owner: Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
shopping-lists
-LJh6sdBJtBCM7DwxPRy
-name: "weekly shopping"
-owner: "mike#gmail.com"
I have a home page after login that shows existing shopping lists on table if they exist. On viewDidLoad() I get shopping list IDs from the user and use those IDs as a reference to get details from shopping-lists.
However, I cant manage to save these data into an array as it gets deleted after closure. How can I do that in a clean way?
override func viewDidLoad() {
super.viewDidLoad()
SVProgressHUD.show()
tableView.allowsMultipleSelectionDuringEditing = false
// Sets user variable - must have
Auth.auth().addStateDidChangeListener { auth, user in
guard let user = user else { return }
self.user = User(authData: user)
// If new user, write into Firebase
self.usersRef.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.hasChild(self.user.uid) {
self.usersRef.child(user.uid).setValue(["email": user.email!])
}
})
// Get shopping lists data from "users/lists"
self.usersRef.child(user.uid).child("lists").observe(.value, with: { snapshot in
// Get list IDs
if snapshot.exists() {
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
self.listNames.append(child.key)
}
}
}
// Use list IDs - to get details
for item in self.listNames {
let itemRef = self.shoppingListsRef.child(item)
itemRef.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
let name = value["name"] as? String ?? ""
let owner = value["owner"] as? String ?? ""
let shoppingList = ShoppingList(name: name, owner: owner)
self.items.append(shoppingList)
}
})
}
})
self.tableView.reloadData()
SVProgressHUD.dismiss()
}
}
(the question is a bit unclear so several parts to this answer to cover all possibilities. This is Swift 4, Firebase 4/5)
You don't really need to query here since you know which nodes you want by their key and they will always be read in the in order of your listNames array. This assumes self.listNames are the keys you want to read in.
for item in listNames {
let itemRef = shoppingListsRef.child(item)
itemRef.observe(.value, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
let name = value["name"] as? String ?? ""
let owner = value["owner"] as? String ?? ""
print(name, owner)
}
})
}
Generally, queries are used when you are searching for something within a node - for example if you were looking for the node that contained a child name of 'weekly shopping'. Other than that, stick with just reading the nodes directly as it's faster and has less overhead. Keep reading...
I also removed the older NSDictionary and went with the Swift [String: Any] and modified your error checking
However, the real issue is reading that node with an .observe by .value. Remember that .value reads in all children of the node and then the children need to be iterated over to get each separate DataSnapshot. Also, .observe leaves an observer on the node notifying the app of changes, which I don't think you want. So this will answer the question as posted, (and needs better error checking)
for item in listNames {
let queryRef = shoppingListsRef
.queryOrdered(byChild: "name")
.queryEqual(toValue: item)
queryRef.observe(.value, with: { (snapshot) in
for child in snapshot.children { //even though there is only 1 child
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let name = dict["name"] as? String ?? ""
let owner = dict["owner"] as? String ?? ""
print(name, owner)
}
})
}
And the answer...
This is probably more what you want...
for item in listNames {
let queryRef = shoppingListsRef
.queryOrdered(byChild: "name")
.queryEqual(toValue: item)
queryRef.observeSingleEvent(of: .childAdded, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let name = dict["name"] as? String ?? ""
let owner = dict["owner"] as? String ?? ""
print(name, owner)
})
}
note the .childAdded instead of .value which presents the snapshot as a single DataSnapshot and doesn't need to be iterated over and the .observeSingleEvent which does not leave an observer attached to each node.
Edit
Based on additonal information, it would be best too change the structure to this
shopping-lists
-LJh6sdBJtBCM7DwxPRy
-name: "weekly shopping"
-uid: "Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2"
and then when the user logs in just query the shopping lists node for any uid that's theirs.

Ambiguous Use of Subscript (Swift 3)

I am using the subscript in the following code incorrectly for this Firebase data pull, but I can't figure out what I am doing wrong. I get an error of Ambiguous use of subscript for the let uniqueID = each.value["Unique ID Event Number"] as! Int line.
// Log user in
if let user = FIRAuth.auth()?.currentUser {
let uid = user.uid
// values for vars sevenDaysAgo and oneDayAgo set here
...
let historyRef = self.ref.child("historyForFeedbackLoop/\(uid)")
historyRef.queryOrdered(byChild: "Unix Date").queryStarting(atValue: sevenDaysAgo).queryEnding(atValue: oneDayAgo).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("user data not found")
}
else {
if let snapDict = snapshot.value as? [String:AnyObject] {
for each in snapDict {
// Save the IDs to array.
let uniqueID = each.value["Unique ID Event Number"] as! Int
self.arrayOfUserSearchHistoryIDs.append(uniqueID)
}
}
else{
print("SnapDict is null")
}
}
})
}
I tried to applying what I learned from this post, but I couldn't figure out what I am missing because I thought I was letting the compiler know what type of dictionary it is with the "as? [String:AnyObject]"
Any thoughts or ideas would be greatly appreciated!
My preferred way of dealing with data is to unwrap the FIRDataSnapshot as late as possible.
ref!.observe(.value, with: { (snapshot) in
for child in snapshot.children {
let msg = child as! FIRDataSnapshot
print("\(msg.key): \(msg.value!)")
let val = msg.value! as! [String:Any]
print("\(val["name"]!): \(val["message"]!)")
}
})
Taking Frank's feedback into account, here is the actual working code I used that follows that approach in case it's helpful.
// Log user in
if let user = FIRAuth.auth()?.currentUser {
let uid = user.uid
// values for vars sevenDaysAgo and oneDayAgo set here
...
let historyRef = self.ref.child("historyForFeedbackLoop/\(uid)")
historyRef.queryOrdered(byChild: "Unix Date").queryStarting(atValue: sevenDaysAgo).queryEnding(atValue: oneDayAgo).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("user data not found")
}
else {
for child in snapshot.children {
let data = child as! FIRDataSnapshot
let value = data.value! as! [String:Any]
self.arrayOfUserSearchHistoryIDs.append(value["Unique ID Event Number"] as! Int)
}
}
})
}