Iterate inside a nested child. Firebase and Swift 3 - ios

Here is my data structure:
{ "ItemData": {
"Item": [
{
"name": "Table",
"measurement ": [
{
"height": 30
},
{
"width": 50
}
]
}
]
}
}
I can currently fetch all the data from Firebase and able to display the name on to a tableView. I am now trying to get the values that are nested inside the 'measurement' i.e. 'height' & 'width'. I have looked at Query Firebase for nested child swift, What's the best way of structuring data on firebase?, Iterate through nested snapshot children in Firebase using Swift and Firebase Swift 3 Xcode 8 - iterate through observe results but still have no clue how to solve this.
This is my Item class:
class Item {
var name: String!
var measurement: String!
var key: String
init(from snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as? [String: Any]
self.name = snapshotValue!["name"] as! String
self.measurement = snapshotValue?["measurement"] as! String
self.key = snapshot.key
}
}
This is the function I use to fetch the item. The ItemManager is a class that has the function to remove and add the array of Item:
func fetchItem() {
let databaseRef = FIRDatabase.database().reference(withPath: "ItemData/Item/")
databaseRef.observe(.value, with: { snapshot in
ItemManager.shared.removeAll()
for item in snapshot.children {
guard let snapshot = item as? FIRDataSnapshot else { continue }
let item = Item(from: snapshot)
ItemManager.shared.additem(item)
print(snapshot)
}
self.tableView.reloadData()
})
}
Please help me if you can :)

As suggested in comment measurement array of dictionary not the String, So if you want to get height and width from it you can get it this way.
class Item {
var name: String!
var heightMeasurement: String!
var widthMeasurement: String!
var key: String
init(from snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as? [String: Any]
self.name = snapshotValue!["name"] as! String
if let array = snapshotValue["measurement"] as? [[String:Any]],
let heightDic = array.first, let height = heightDic["height"],
let widthDic = array.last, let width = widthDic["width"] {
self.heightMeasurement = "\(height)"
self.widthMeasurement = "\(width)"
print(height, width)
}
else {
self.heightMeasurement = "" //set some default value
self.widthMeasurement = "" //set some default value
}
self.key = snapshot.key
}
}
Note: If your array having more than two objects than to get the height and width you need to subscripting the array index first to get the dictionary and then access its key according to get your value.

Related

Access childAutoID to update selected child value in Firebase

In order to populate my tableView, I append items (created from a struct) to a local array:
func loadList() {
var newAnnotations: [AnnotationListItem] = []
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").queryOrderedByKey().observeSingleEvent(of: .value, with: {snapshot in
for item in snapshot.children {
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
annotationList = newAnnotations
self.tableView.reloadSections([0], with: .fade)
})
}
}
When I click a specific row, I am taken to a DetailViewController where it is only a large UITextView (named notes). The UITextView.text displayed is based on the selected indexPath.row and the "notes" value is retrieved from the array. Now the user is able to type some text and when they are done, the textViewDidEndEditing function is called:
func textViewDidEndEditing(_ textView: UITextView) {
notes.resignFirstResponder()
navigationItem.rightBarButtonItem = nil
let newNotes = self.notes.text
print(newNotes!)
}
Now I'd like to updateChildValues to newNotes to the child node "notes" in my JSON:
"users" : {
"gI5dKGOX7NZ5UBqeTdtu30Ze9wG3" : {
"annotations" : {
"-KuWIRBARv7osWr3XDZz" : {
"annotationSubtitle" : "1 Cupertino CA",
"annotationTitle" : "Apple Infinite Loop",
"notes" : "Does it work?!",
}
How can I access the selected autoID so I can update the specific notes node. So far the best I have is:
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(somehow access the specific childID).updateChildValues(["notes": newNotes])
Any help will be greatly appreciated. Thanks in advance
UPDATE
The annotationListItem struct is created:
struct AnnotationListItem {
let key: String?
var annotationTitle: String?
let annotationSubtitle: String?
let notes: String?
let ref: DatabaseReference?
init(key: String = "", annotationTitle: String, annotationSubtitle: String, notes: String) {
self.key = key
self.annotationTitle = annotationTitle
self.annotationSubtitle = annotationSubtitle
self.notes = notes
self.ref = nil
}
init(snapshot: DataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
annotationTitle = snapshotValue["annotationTitle"] as? String
annotationSubtitle = snapshotValue["annotationSubtitle"] as? String
notes = snapshotValue["notes"] as? String
ref = snapshot.ref
}
init(Dictionary: [String: AnyObject]) {
self.key = Dictionary["key"] as? String
self.annotationTitle = Dictionary["annotationTitle"] as? String
self.annotationSubtitle = Dictionary["annotationSubtitle"] as? String
self.notes = Dictionary["notes"] as? String
self.ref = nil
}
func toAnyObject() -> Any {
return [
"annotationTitle": annotationTitle as Any,
"annotationSubtitle": annotationSubtitle as Any,
"notes": notes as Any
]
}
}
UPDATE
This is how the annotationListItem is created to be stored in Firebase:
// Using the current user’s data, create a new AnnotationListItem that is not completed by default
let uid = Auth.auth().currentUser?.uid
guard let email = Auth.auth().currentUser?.email else { return }
let title = placemark.name
let subtitle = annotation.subtitle
let notes = ""
// declare variables
let annotationListItem = AnnotationListItem(
annotationTitle: title!,
annotationSubtitle: subtitle!,
notes: notes)
// Add the annotation under their UID
let userAnnotationItemRef = uidRef.child(uid!).child("annotations").childByAutoId()
userAnnotationItemRef.setValue(annotationListItem.toAnyObject())
I think you only need to do this:(since you have declared the note as global)
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(note.key).updateChildValues(["notes": newNotes])
inside the method where you change the notes
If I am not mistaken you are creating an array of a custom object?
var newAnnotations: [AnnotationListItem] = []
You could do something like: var newAnnotations: [(key: String, value: [String : Any])] = [] (Any only if you are going to have Strings, Integers, ect. If it'll only be String then specify it as a String.
Accessing the key would be: newAnnotations[indexPath.row].key in your cellForRowAtIndex of your tableView. Accessing values would be: newAnnotations[indexPath.row].value["NAME"].
You can have a separate array that holds the key and just append it at the same time as your population:
for item in snapshot.children {
guard let itemSnapshot = task as? FDataSnapshot else {
continue
}
let id = task.key //This is the ID
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
Another thing you could do is go up one more level in your firebase call:
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").observeSingleEvent(of: .value, with: {snapshot in
if snapshot is NSNull{
//Handles error
} else{
if let value = snapshot.value as? NSDictionary{ //(or [String: String]
//set localDictionary equal to value
}
}
self.tableView.reloadSections([0], with: .fade)
})
}
And then when you select a row: let selectedItem = localDictionary.allKeys[indexPath.row] as! String //This is the ID you pass to your viewController.

Sorting Firebase Realtime Database data in Swift

I need to show these data in a table view, but they have been on the table view in mixing order. How can I put them in the same order as in the database. Please help, I will deeply appreciated.
First image shows my iPhone screen after running the project, second one is the database. I want them exact order as in the database.
I hope periods objects look like this:
struct PeriodItem {
let key: String
let periodEnd: String
let periodName: String
let periodStart: String
let ref: FIRDatabaseReference?
init(periodEnd: String, periodName: String, periodStart: String, key: String = "") {
self.key = key
self.periodEnd = periodEnd
self.periodName = periodName
self.periodStart = periodStart
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
periodEnd = snapshotValue["periodEnd"] as! String
periodName = snapshotValue["periodName"] as! String
periodStart = snapshotValue["periodStart"] as! String
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"periodEnd": periodEnd,
"periodName": periodName,
"periodStart": periodStart,
"key": key
]
}
}
So
When you fill your array of Periods fully, just use sorting:
// periods - array of objects from firebase database
let yourTableViewPeriodsArray = periods.sorted(by: { $0.key < $1.key }) // maybe ">" instead of "<"
Then:
DispatchQueue.main.async {
self.tableView.reloadData()
}
Hope it helps.

Firebase Retrieve Data - Could not cast value

First, I have checked these answers that do not help me :
Swift JSON error, Could not cast value of type '__NSArrayM' (0x507b58) to 'NSDictionary' (0x507d74)
Get data from Firebase
When retrieving data from Firebase (3.x), I have an error that occurs which is :
Could not cast value of type '__NSArrayM' (0x10ca9fc30) to 'NSDictionary' (0x10caa0108).
with this code and tree :
Tree :
Retrieving function :
func retrievePlanes() {
print("Retrieve Planes")
ref = FIRDatabase.database().reference(withPath: "results")
ref.observe(.value, with: { snapshot in
var newItems: [Planes] = []
for item in snapshot.children {
let planesItem = Planes(snapshot: item as! FIRDataSnapshot)
newItems.append(planesItem)
}
self.planes = newItems
self.tableView.reloadData()
})
}
Planes.swift - To manage the data
import Foundation
import Firebase
import FirebaseDatabase
struct Planes {
let key: String!
let name: String!
let code:String!
let flightRange: Int?
let typicalSeats: Int?
let maxSeats: Int?
let wingSpan: String!
let takeoffLength: Int?
let rateClimb: Int?
let maxCruiseAltitude: Int?
let cruiseSpeed: String!
let landingLength: Int?
let engines: String!
let votes: Int?
let data: String!
let imagePlane:String!
let imageTakenFrom: String!
let ref: FIRDatabaseReference?
init(name: String, code: String, flightRange: Int, typicalSeats: Int, maxSeats: Int, wingSpan: String, takeoffLength: Int, rateClimb: Int, maxCruiseAltitude: Int, cruiseSpeed: String, landingLength: Int, engines: String, votes: Int, data: String, imagePlane: String, imageTakenFrom: String, key: String = "") {
self.key = key
self.name = name
self.code = code
self.flightRange = flightRange
self.typicalSeats = typicalSeats
self.maxSeats = maxSeats
self.wingSpan = wingSpan
self.takeoffLength = takeoffLength
self.rateClimb = rateClimb
self.maxCruiseAltitude = maxCruiseAltitude
self.cruiseSpeed = cruiseSpeed
self.landingLength = landingLength
self.engines = engines
self.votes = votes
self.data = data
self.imagePlane = imagePlane
self.imageTakenFrom = imageTakenFrom
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
ref = snapshot.ref
key = snapshot.key
let snapshotValue = snapshot.value as! [String:AnyObject]
name = snapshotValue["name"] as! String
code = snapshotValue["code"] as! String
flightRange = snapshotValue["intFlightRange"] as? Int
typicalSeats = snapshotValue["intTypicalSeats"] as? Int
maxSeats = snapshotValue["intMaxSeats"] as? Int
wingSpan = snapshotValue["wingSpan"] as! String
takeoffLength = snapshotValue["intTakeoffLength"] as? Int
rateClimb = snapshotValue["intRateClimb"] as? Int
maxCruiseAltitude = snapshotValue["intMaxCruiseAltitude"] as? Int
cruiseSpeed = snapshotValue["cruiseSpeed"] as! String
landingLength = snapshotValue["intLandingLength"] as? Int
engines = snapshotValue["engines"] as! String
votes = snapshotValue["votes"] as? Int
data = snapshotValue["data"] as! String
imagePlane = snapshotValue["planeImage"] as! String
imageTakenFrom = snapshotValue["imageTakenFrom"] as! String
}
on the line : let snapshotValue = snapshot.value as! [String:AnyObject]
I suppose that is due to the snapshot value that can't be retrieved under [String:AnyObject] because of the Int below.
(It is working when I only have String in another case).
I also know that Firebase "converts" the JSON tree to these objects [link]:
NSString
NSNumber
NSArray
NSDictionnary
but I can't figure out what has to be changed in the snapshot.value line to make it work.
Thanks for your help.
EDIT : I just sent a troubleshooting request. Will post updates.
EDIT 2: See Jay's answer. In my case the tree was wrong.
I took your code and shrunk it down a bit for testing, and it's working. (note Firebase 2.x on OS X and Swift 3 but the code is similar)
Firebase structure:
"what-am" : {
"results" : [ {
"code" : "738/B738",
"data" : "Boeing",
"engines" : "Rolls"
}, {
"code" : "727/B727",
"data" : "Boeing",
"engines" : "Pratt"
} ]
}
Here's the Planes struct
struct Planes {
var code:String!
var data: String!
var engines: String!
init(code: String, data: String, engines: String ) {
self.code = code
self.data = data
self.engines = engines
}
init(snapshot: FDataSnapshot) {
let snapshotValue = snapshot.value as! [String:AnyObject]
code = snapshotValue["code"] as! String
data = snapshotValue["data"] as! String
engines = snapshotValue["engines"] as! String
}
}
and then the code that reads in two planes, populates and array and then prints the array.
let ref = self.myRootRef.child(byAppendingPath: "what-am/results")!
ref.observe(.value, with: { snapshot in
if ( snapshot!.value is NSNull ) {
print("not found")
} else {
var newItems: [Planes] = []
for item in (snapshot?.children)! {
let planesItem = Planes(snapshot: item as! FDataSnapshot)
newItems.append(planesItem)
}
self.planes = newItems
print(self.planes)
}
})
and finally the output
[Swift_Firebase_Test.Planes(code: 738/B738, data: Boeing, engines: Rolls),
Swift_Firebase_Test.Planes(code: 727/B727, data: Boeing, engines: Pratt)]
Key and name are nil as I removed then from the Planes structure.
The line you asked about
let snapshotValue = snapshot.value as! [String:AnyObject]
is valid as the snapshot contains a series of key:value pairs so String:AnyObject works.
This line changed due to Swift 3
for item in (snapshot?.children)!
but other than that, the code works.
Try this to ensure you are reading the correct node. This reads the above structure and prints out each engine type. (tested and works)
let ref = self.myRootRef.child(byAppendingPath: "what-am/results")!
ref.observe(.value, with: { snapshot in
if ( snapshot!.value is NSNull ) {
print("not found")
} else {
for child in (snapshot?.children)! {
let snap = child as! FDataSnapshot
let dict = snap.value as! [String: String]
let engines = dict["engines"]
print(engines!)
}
}
})
I think you are having an extra array in your results key-value on the firebase data.
You should try removing that array or
You may retrieve dictionary from first index of the array like;
// .. your code
let snapshotValue = (snapshot.value as! [AnyObject])[0] as! [String:AnyObject];
// .. your code
In your struct class make sure of these things:-
Avoid declaring your variables as :Int? because that's practically nil, change them to :Int!
Your key in your firebase is an Int and you are declaring your key in struct as let key: String!, Change it to let key: Int!
Prefer your snapshot dictionary declaration as let snapshotValue = snapshot.value as! [AnyHashable:Any] (as per swift 3)
Then your init function to :-
Just change the line
let snapshotValue = snapshot.value as! [String:AnyObject]
To
let snapshotValue = (snapshot.value as! NSArray)[0] as! [String:AnyObject]
update FIRDataSnapshot to DataSnapshot Swift 4
Below is an example for Swift 4. Where you need to change FIRDataSnapshot to DataSnapshot
func fetchChats(chatId: String) {
ref.child("chats").child("SomeChildId").observe(.value) { (snapshot) in
for child in snapshot.children {
let data = child as! DataSnapshot //<--- Update this line
let dict = data.value as! [String: AnyObject]
let message = dict["message"]
print(message!)
}
}
}

How to retrieve firebase database properly?

I am trying to retrieve the data from firebase database. However, I cannot get my local variables assigned to the values of the database. I am using the following classes and methods.
class Episode {
var title: String?
var description: String?
var location: String?
var discount: String?
var star: Int?
init() {
self.title = ""
self.description = ""
self.location = ""
self.discount = ""
self.star = 0
}
This is my method for pulling the data from the databse
func getValues() -> Episode {
let rootRef = FIRDatabase.database().reference().child("Restaurants").child("The Kafe")
let descriptionRef = rootRef.child("Description")
let discountRef = rootRef.child("Discount")
let locationRef = rootRef.child("Location")
let starRef = rootRef.child("Star")
let episode = Episode()
descriptionRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.description = snap.value as? String
}
discountRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.discount = snap.value as? String
}
locationRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.location = snap.value as? String
}
starRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.star = snap.value as? Int
print(episode.description!)
}
return episode
}
When I print out the values of the returned episode, they are all empty. However, when I print the values within the closure itself (Eg. if I do print(episode.description) within the obserEventType closure, it works fine. But if I print it outside it is empty.
I think I am missing something fundamental about swift or firebase. I am new to iOS programming so any help would be greatly appreciated.
Only inside the first observer you will have the value the return will always be nil, that is because only the return is trying to work in a sync way while firebase will always work in an async way
rootRef.observeEventType(.Value, withBlock: {(snap) in
let ep: Dictionary<String,AnyObject?> = [
"title": snap.childSnapshotForPath("Title").value as? String,
"description": snap.childSnapshotForPath("Description").value as? String,
"location": snap.childSnapshotForPath("Location").value as? String,
"discount": snap.childSnapshotForPath("Discount").value as? String,
"star": (snap.childSnapshotForPath("Star").value as? NSNumber)?.integerValue,
]
//Here you have your data in your object
episode = Episode(key: snap.key, dictionary: ep)
})
rootRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
print(snap.childSnapshotForPath("Title").value as? String)
}
return episode!
Also if you want to get it from a function like that you should probably use observeSingleEventType.
You need to rethink flow of your code because you are expecting firebase to work synchronously when it is always asynchronous. The way you have your getValues function will never work.
To solve this issue you should read about async execution and callbacks in swift.
All Firebase events are asynchronous so they are executed in a non-sequential way, that is why you only have access to the data inside the context of the callback...if you put a print outside the callback it is executed in a synchronous way so it gets executed before the callback, that is why it is in its initial status
1) You only need the rootRef, delete the rest
let ref = FIRDatabase.database().reference().child("Restaurants").child("The Kafe")
2) You only need one observer
var episode:Episode? = nil
rootRef.observeEventType(.Value,withBlock: {(snap) in
let ep:Dictionary<String,AnyObject?> = [
"title":snap.childSnapshotForPath("title").value as? String,
//Etc...
"star":(snap.childSnapshotForPath("price").value as? NSNumber)?.integerValue,
]
//Here you have your data in your object
episode = Episode(key:snap.key,dictionary:ep)
}
3) your episode class can be like this
class Episode {
private var _key:String!
private var _title:String?
//Etc.....
private var _star:Int?
var key:String!{
return _key
}
var title:String?{
return _title
}
//Etc....
var star:Int?{
return _star
}
init(key:String!, title:String?,//etc...., star:Int?){
self._key = key
self._title = title
//Etc....
}
init(key:String,dictionary:Dictionary<String,AnyObject?>){
_key = key
if let title = dictionary["title"] as? String{
self._title = title
}
//Etc...
if let star = dictionary["star"] as? Int{
self._star = star
}
..
}
}

how parsing Firebase FDatasnapshot json data in swift

I'm having issue getting data from Firebase.
schema is
{
title: "dog",
images: {
main: "dog.png",
others: {
0: "1.png",
1: "2.png",
2: "3.png"
}
}
}
how can i parse FDataSnapshot to swift model??
Firebase is a NoSQL JSON database and has no schema and no tables. Data is stored with a 'tree' structure with nodes; parents and children.
You don't need to parse Firebase JSON data to access it, you can access it directly.
FDataSnapshots contain a .key, which is it's parent key in Firebase and .value. .Value may contain one node, or multiple nodes. The Value will have key:value pairs representing the data within the snapshot
So for your example you will have a Firebase structure like this
dogs
dog_id_0
title: "dog"
type: "Alaskan Malamute"
images:
main: "dog.png"
others:
0: "1.png"
1: "2.png"
dog_id_1
title: "another dog"
type: "Boxer"
images:
main: "another_dog.png"
others:
0: "3.png"
1: "4.png"
So, say you want to read in each dog_id_x node one at a time and print some values.
var ref = Firebase(url:"https://your-app.firebaseio.com/dogs")
ref.observeEventType(.ChildAdded, withBlock: { snapshot in
println(snapshot.value.objectForKey("title"))
println(snapshot.value.objectForKey("type"))
})
This will output
dog
Alaskan Malamute
another dog
Boxer
The dog_id_0 and dog_id_1 are node names created with the Firebase childByAutoId.
You could just as easily create a Dog class, and pass it the FDataSnapshot which will populate the class from the data within the snapshot.
February 2017 Update, Swift 3 Xcode 8
Since a lot of things with Swift3 and Firebase have changed by the time this question was asked I will provide an updated way to parse Firebase data:
let userID = FIRAuth.auth()?.currentUser?.uid
//I am registering to listen to a specific answer to appear
self.ref.child("queryResponse").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
//in my case the answer is of type array so I can cast it like this, should also work with NSDictionary or NSNumber
if let snapshotValue = snapshot.value as? NSArray{
//then I iterate over the values
for snapDict in snapshotValue{
//and I cast the objects to swift Dictionaries
let dict = snapDict as! Dictionary<String, Any>
}
}
}) { (error) in
print(error.localizedDescription)
}
Try to play with this:
func makeItems(from snapshot: DataSnapshot) -> [SimpleItem] {
var items = [SimpleItem]()
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let item = SimpleItem(parentKey: snap.key, dictionary: postDictionary)
items.append(item)
}
}
}
return items
}
func loadItems() {
firebaseService.databaseReference
.child("items")
.queryOrdered(byChild: "date")
.queryLimited(toLast: 5)
.observeSingleEvent(of: .value) { snapshot in
let items = self.makeItems(from: snapshot)
print("🧀 \(items)")
}
}
class SimpleItem {
var parentKey: String?
var id: String?
var description: String?
init(parentKey: String, dictionary: [String : AnyObject]) {
self.parentKey = parentKey
id = dictionary["id"] as? String
description = dictionary["description"] as? String
}
}
You could parse it maually with Dictionary or you can use my library.
Example code for your case:
func main(){
let root=SnapshotParser().parse(snap: Snapshot, type: Root.self)
}
class Root: ParsableObject {
var title:String?=nil
var images:Images?=nil
required init(){}
func bindProperties(binder: SnapshotParser.Binder) {
binder.bindField(name: "title", field: &title)
binder.bindObject(name: "images", field: &images)
}
}
class Images: ParsableObject {
var main:String?=nil
var others:[Int:String]?=nil
required init(){}
func bindProperties(binder: SnapshotParser.Binder) {
binder.bindField(name: "main", field: &main)
binder.bindDictionary(name: "others", dict: &others)
}
}
This will parse all the snapshot children in the single object and convert it in array and you can easily parse the array of children with index
if let snap = snapshot.children.allObjects as? [DataSnapshot]{
print(snap)
for (index,val) in snap.enumerated(){
print("values")
print(val)
print(val.value)
}
}

Resources