After reading data from Firestore cells in tableView randomly reorder - ios

I'm working on application that prints name of place that has been visited according to its ID. I'm storing data in several nodes node called "placesExploredByUsers/userID" stores data about IDs of places that user have visited before and node "databaseOfPlaces" stores all IDs of places with additional info (name, location, coordinates etc.), so it works like foreign key in SQL. All of my data is stored in Firebase.
But, I'm having problem with ordering my cells in tableView in Swift. I've tried several options and this is my last modification. The problem is, that my cells are randomly reordering everytime I come to the viewController with tableView.
This is function, that should handle it.
Can anyone please help me? Thanks in advance
func getDataToTable(){
// setting the firebase reference
ref = Database.database().reference()
let userID = (Auth.auth().currentUser?.uid)!
// getting information about places that user has visited
Database.database().reference().child("placesExploredByUsers").child(userID).queryOrderedByValue().observeSingleEvent(of: .value) { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
// creating array of all ID of places that user has visited
let idsOfAllPlaces = Array(dictionary.keys)
for id in idsOfAllPlaces {
// getting information about places from database of ID of all places that are in app
Database.database().reference().child("databaseOfPlaces").child(id).queryOrderedByValue().observeSingleEvent(of: .value) { (snapshot1) in
if let dictionary = snapshot1.value as? [String: AnyObject]{
// getting information about name
self.random = dictionary["name"]! as! String
// updating the table view
self.postData.append(self.random)
self.sortedList(array: self.postData)
}
}
}
}
}
}

as per documentation
By default, a query retrieves all documents that satisfy the query in
ascending order by document ID. You can specify the sort order for
your data using orderBy(), and you can limit the number of documents
retrieved using limit().
Note: An orderBy() clause also filters for existence of the given field. The result set will not include documents that do not contain the given field.
So use order(by: "databaseOfPlaces")
func getDataToTable(){
// setting the firebase reference
ref = Database.database().reference()
let userID = (Auth.auth().currentUser?.uid)!
// getting information about places that user has visited
Database.database().reference().child("placesExploredByUsers").child(userID).order(by: "databaseOfPlaces").observeSingleEvent(of: .value) { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
// creating array of all ID of places that user has visited
let idsOfAllPlaces = Array(dictionary.keys)
for id in idsOfAllPlaces {
// getting information about places from database of ID of all places that are in app
Database.database().reference().child("databaseOfPlaces").child(id).queryOrderedByValue().observeSingleEvent(of: .value) { (snapshot1) in
if let dictionary = snapshot1.value as? [String: AnyObject]{
// getting information about name
self.random = dictionary["name"]! as! String
// updating the table view
self.postData.append(self.random)
self.sortedList(array: self.postData)
}
}
}
}
}
}

The problem is that you call snapshot1.value as? [String: AnyObject], which means the order is lost (because items in a dictionary have no defined order).
To process the results in the order you requested them, loop over snapshot1.children. Something like:
for snap in snap1.children.allObjects as! [DataSnapshot] {
self.random = snap.value["name"]! as! String
self.postData.append(self.random)
self.sortedList(array: self.postData)
}
Also see:
Firebase getting data in order
iOS - Firebase Filter query
How to properly use queryOrderedByValue

Related

How can I convert a Firebase Database Snapshot into an Array List in Swift?

I am making an app using Swift and Firebase. I want to get an array list containing all of the numbers under a certain user id (images attached below). What I mean by this is I want to be able to call a function that returns an array list containing every integer (from low to high) placed as a child of the user id, but not containing their values (in this case "true"). I have already gotten a snapshot of the data (see code below), but I am unsure as to what to do now.
My Code:
func likeToLikeForAll() {
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("Liked Movies").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
print()
print("SNAP:")
print(snapshot.value!)
print()
}
})
}
The function prints:
This is an image of the realtime database:
Create an array from the keys
let array = Array(dictionary.keys)

How to loop through Firebase data

How to loop through Firebase data (childs) which are actually objects and access to their properties in Swift 4?
As a beginner with Swift, I am trying to loop through data I am retrieving from Firebase, and I am trying to access to properties of those objects. Seems much more complicated then it should be in swift (just my subjective opinion)
As per documentation on the Firebase site this is what I have
_commentsRef.observe(.value) { snapshot in
for child in snapshot.children {
// Access to childs here ...
}
}
Now, combining this above and as per tutorials and explanations (btw was not able to find neither one which explains this fully) which I found on net, this is what I have:
ref.child("activities").child("list").observeSingleEvent(of: .value, with: { (snapshot) in
// The list i got here is the list of the childs which are objects
// Lets loop through that list and pull properties we need
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
}
})
The print in the loop will properly display object with all of its properties, but I am not able to access to these properties. Accessing to it with something like "child.value.title" is resulting with error "Value of type 'Any' has no member 'title'"
Do I need to convert child.value to something else, maybe to cast it or to convert it somehow to property accessible JSON or something like that?
If you call value on a snapshot that contains multiple properties, what you get back is a NSDictionary with the property names as the keys. So to get the value of the title key you'd do:
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
let dict = child.value as? [String : AnyObject] ?? [:]
print(dict["title"])
}
Alternatively you can use the other members of DataSnapshot to navigate to the title property and then call .value on that:
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
print(child.childSnapshot(forPath: "title").value)
}
See DataSnapshot.value and the first sample in the Firebase documentation on reading data.

Cannot successfully reload UITableView elements when parsing data from Firebase

I'm making an app where users can buy and sell tickets. Users are able to create a new ticket and it successfully uploads to firebase however a reference to the ticket ID is stored in the user data which references the ticket id in the ticket data. The structure of the database is below:
DATABASE
USERS
TICKETS
TICKETS
TICKET INFO
USER
USER INFO AND TICKET ID OF TICKETS THEY ARE SELLING
My problem is that the first time I load the tickets from the selling tickets it's fine. However when the user adds a new ticket that they are selling, the table view loads everything twice.
override func viewWillAppear(_ animated: Bool) {
self.tickets = []
DataService.ds.REF_USER_CURRENT.child("selling").observe(.value, with: { (snapshot) in //HERE WE REFERNCE OUR SINGELTON CLASS AND OBSERVE CHAMGE TO THE POSTS OBJECT
self.tickets = [] //WE CLEAR THE POSTS ARRAY BEFORE WE START MANIPULATION TO MAKE SURE THAT WE DONT REPEAT CELLS
if let snapshot = snapshot.children.allObjects as? [DataSnapshot]{
print("ADAM: \(snapshot)")//CHECKING THAT THE OBJECTS EXIST AS AN ARRAY OF DATA SNAPSHOTS
for snap in snapshot {
DataService.ds.REF_TICKETS.child(snap.key).observe(.value, with: { (snapshot) in
if let ticketDict = snapshot.value as? Dictionary<String, AnyObject>{
let ticket = Ticket(ticketID: snap.key, ticketData: ticketDict)
self.self.tickets.append(ticket)
}
self.sell_ticketsTableView.reloadData()
})
}
}
//self.sell_ticketsTableView.reloadData()
self.tickets = []//RELAOD THE DATA
})
}
I'm not quite sure where I have gone wrong.
Please change your code to this. I have added the part where you clear your array
override func viewWillAppear(_ animated: Bool) {
self.removeAll() // This is how you clear your array
DataService.ds.REF_USER_CURRENT.child("selling").observe(.value, with: { (snapshot) in //HERE WE REFERNCE OUR SINGELTON CLASS AND OBSERVE CHAMGE TO THE POSTS OBJECT
self.tickets = [] //WE CLEAR THE POSTS ARRAY BEFORE WE START MANIPULATION TO MAKE SURE THAT WE DONT REPEAT CELLS
if let snapshot = snapshot.children.allObjects as? [DataSnapshot]{
print("ADAM: \(snapshot)")//CHECKING THAT THE OBJECTS EXIST AS AN ARRAY OF DATA SNAPSHOTS
for snap in snapshot {
DataService.ds.REF_TICKETS.child(snap.key).observe(.value, with: { (snapshot) in
if let ticketDict = snapshot.value as? Dictionary<String, AnyObject>{
let ticket = Ticket(ticketID: snap.key, ticketData: ticketDict)
self.tickets.append(ticket)
}
self.sell_ticketsTableView.reloadData()
})
}
}
//self.sell_ticketsTableView.reloadData()
})
}
You are observing the value of what a user is selling, which means every time they add something to this list, your listener will trigger and give you the new value of users/$uid/selling in its entirety.
This is why you are seeing double when the user adds another ticket; the listener is triggered and you append each ticket to the array again. You can get around this by checking if the ticket is already in the array before you append it however, your current implementation can be improved.
Instead of using observe(.value, you should use .childAdded. The listener will trigger every time a new child is added and only give you that specific child snapshot.
The listener will initially trigger for each child at that reference and you can append them to the array individually. However, once all the children have been loaded, the next child to be added will trigger this listener, which you can append to the array.

Storing children of Firebase Database in an array

I have set up a Firebase database that has a parent and many different children. I am attempting to save all of the children's keys into an array that I can access elsewhere in the code.
// Used to get the children
rootRef.child(partyName).observe(.childAdded, with: { (snapshot) in
var newItems = [FIRDataSnapshot]()
for item in snapshot.children {
newItems.append(item as! FIRDataSnapshot)
}
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict{
let keyID = each.key
saves.append(keyID)
}
}
})
Unfortunately, most of the "solutions" that I have found online simply print the retrieved data or add it to a Table View. I simply want all the children to be saved in an array that I can access later. Is this possible? If so, how would I do it?
Thanks
The proper way of storing the retrieved data is to store the key-value pairs in an NSDictionary or a Swift Dictionary. As Sachin Vas said, it makes no sense to store just the keys because then you'd have no relation back to the values.
Nevertheless, to answer your question, the code you provided in your question does what you're asking. If saves is a global or static array in some class, it would be accessible globally throughout your application and would contain all the retrieved keys.
The right way of storing the retrieved data would work just the same way, except saves would be a Dictionary.
rootRef.child(partyName).observe(.childAdded, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject] {
saves = snapDict // where saves is declared as Dictionary<String, AnyObject>
}
})

Add value instead of change value In Firebase with Swift

I would like to save and retrieve features to and from Firebase into a TableView.
The child I would like to save them under is the uid (unique user id)
so a feature would look like this in the database:
Firebase database
The ideal situation, is how the "derde" is saved, so the uid as a key and "derde" as the value.
#IBAction func saveButtonPressed(sender: AnyObject) {
let featureContents = addFeatureTextField.text
if featureContents != "" {
// Build the new Feature.
let newFeature: String = featureContents!
let ref = DataService.dataService.FEATURE_REF.childByAppendingPath(uid)
ref.setValue(newFeature)
where uid is a String, retrieved from authdata somewhere else in the code.
If I save it like this, it saves it to the specific uid path. If I want to add another feature by clicking on the + in the TableViewController, it saves it to the same path, so the Firebase database is updated with the new value and so instead of two features you only end up with one updated feature.
You can prevent this by working with the chilByAutoId() method, to save a list of items. The code would look like this:
#IBAction func saveButtonPressed(sender: AnyObject) {
let featureContents = addFeatureTextField.text
if featureContents != "" {
// Build the new Feature.
let newFeature: String = featureContents!
let ref = DataService.dataService.FEATURE_REF.childByAutoId().childByAppendingPath(uid)
ref.setValue(newFeature)
via this way, a feature is saved, as you can see in the above image at: "vierde"
This allows you to save multiple features with all the same uid, but different autoId.
But, if I save it like this, my tableView stays empty. The TableViewController is like this:
DataService.dataService.FEATURE_REF.observeEventType(.Value, withBlock: { snapshot in
// The snapshot is a current look at our features data.
print("The features in the tableView should be \(snapshot.value)")
self.features = []
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
// Make our features array for the tableView.
if let postDictionary = snap.value as? String {
print("All in")
let key = snap.key
let feature = Feature(key: key, value: postDictionary)
// Items are returned chronologically, but it's more fun with the newest features first.
self.features.insert(feature, atIndex: 0)
}
}
}
// Be sure that the tableView updates when there is new data.
self.tableView.reloadData()
})
}
Problem lies in this code: if let postDictionary = snap.value as? String {
This conditional binding does not succeed, because the value is not a String, but the autoId key has no value, only the child under it which is the uid has a value "vierde"
Two possible solutions which I am asking you guys:
1) How can I save multiple features with the same uid without using the autoId?
2) If I am obliged to use the autoId, how can I make sure it observes the value of the uid key under the autoId, instead of the non existing value of the autoId.
Thanks for your help!
I think the answer to the question is to build a dictionary out of the key:value pairs of data and store that as a child of your uid node
let featureDict = [ "feature_0": "cool feature", "feature_1": "great feature"]
let ref = DataService.dataService.FEATURE_REF.childByAppendingPath(uid)
ref.setValue(featureDict)
results in
the_uid
feature_0: "cool feature"
feature_1: "great feature"
The limitation here is the key's names, and then the ability to add even more data about each feature.
Here's a potentially better option
the_uid
auto_id_0
feature_name: #"cool feature"
summary: "Everything you'd ever want to know about this feature"
auto_id_1
feature_name: #"great feature"
summary: "Info about this great feature"
The auto_id_x is generated by autoId and allows you to add however many features you want, change their names and summaries. etc. The children of each auto_id_x are (or could be) stored in a dictionary and saved per the above example.

Resources