I have a json database on firebase and trying to get them and put into local array of dictionaries.
My json model on Firebase
My struct model is also like below
struct Places {
var type:String!
var country:String!
var name:String!
var image:String!
var coords:[Coords]!
init(type: String, country: String, name: String, image: String, coords: [Coords]) {
self.type = type
self.country = country
self.name = name
self.image = image
self.coords = coords
}
init(snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as! [String:Any]
type = snapshotValue["type"] as! String
country = snapshotValue["country"] as! String
name = snapshotValue["name"] as! String
image = snapshotValue["image"] as! String
coords = snapshotValue["coords"] as! [Coords]!
}
}
And also [Coords] struct model like below:
struct Coords {
var latStart:String!
var latEnd:String!
var lonStart:String!
var lonEnd:String!
init(latStart: String, latEnd: String, lonStart: String, lonEnd: String) {
self.latStart = latStart
self.latEnd = latEnd
self.lonStart = lonStart
self.lonEnd = lonEnd
}
}
And I am trying to get and put json data by below code:
placesRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
print("data not exist")
return
}
var plc: [Places] = []
for eachPlace in (snapshot.children){
let place = Places(snapshot: eachPlace as! FIRDataSnapshot)
plc.append(place)
}
self.allPlaces = plc
The problem is that I can get the array of dictionary except coords dictionary inside array of dictionary. [Coords] dictionary seems null and I would like to know what the problem is. Thanks for any suggestion.
Because snapshotValue["coords"] as! [Coords]! are not Coords yet. They are just dictionaries. You have to go through each dictionary in snapshotValue[“coords”] and init a Coords object, then when you’re finished group them all into an array and assign it to self.coords of the Places struct. The map function is really convenient for this.
Example:
I would change the Coords init function to something like:
init(dictionary: [String : AnyObject]) {
self.latStart = dictionary["lat1"] as? String
//...
//...
}
Then in Places init use something like:
coords = (snapshotValue["coords"] as? [[String : AnyObject]])?.map({ Coord(dictionary: $0) })
I didn't test this and making some assumptions here, but you should be able to make something similar work.
Related
I have a database in firebase which looks as follows:
I need to get the values of nameID, tutorID, and imageURL and assign them to variables in Swift 4. Here is what I have so far in XCode:
let ref = Database.database().reference().child("students").child("student1")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
print (dictionary)
let Obj = studentInformation(nameID: " ", tutorID: " ", imageURL: " ")
Obj.imageURL = dictionary["photoID"] as? String
Obj.nameID = dictionary["nameID"] as? String
Obj.tutorID = dictionary["tutorID"] as? String
self.studentInfo.append(Obj)
}, withCancel: nil)
For the studentInformation class, I have declared it as such:
class studentInformation {
var nameID: String?
var tutorID: String?
var imageURL: String?
init(nameID: String?, tutorID: String?, imageURL: String?) {
self.nameID = nameID
self.tutorID = tutorID
self.imageURL = imageURL
}
}
I can't seem to get it to work correctly, as it's able to get the values from the database, but it is not able to assign it to the local variables I have in XCode. Any help would be appreciated. Thanks in advance
Create an optional initializer for in the Object and determine which variables should be optional (ex: only the imageURL is optional in the example below, and the nameID and tutorID have to be Strings otherwise the init will return nil):
init?(dictionary: [String : Any]) {
guard let nameId = dictionary["nameID"] as? String,
let tutorID = dictionary["tutorID"] as? String else { return nil }
let imageURL = dictionary["imageURL"] as? String
self.init(nameID: nameID, tutorID: tutorID, imageURL: imageURL)
}
Then, in the Firebase listener you can create the object like this:
// Returns Optional(obj)
let obj = studentInformation(dictionary: dictionary)
or
// Add object to array
if let obj = studentInformation(dictionary: dictionary) { self.studentInfo.append(obj) }
I am completely new to swift and firebase, and I am having difficulties in retrieving array-elements from firebase database
So this is my firebase database
I can retrieve the other elements like this:
database reference
class ViewController: UIViewController {
var ref: FIRDatabaseReference?
let fileName : String = "jsonFile"
method
func parseFirebaseResponse() {
ref?.child("Vandreture").child(fileName).observe(.value, with:
{ (snapshot) in
let dict = snapshot.value as? [String: AnyObject]
let navn = dict!["navn"] as? String
print(navn as Any)
let type = dict!["type"] as? String
print(type as Any)
let Længde = dict!["length"] as? String
print(Længde as Any)
let link = dict!["link"] as? String
print(link as Any)
})
}
the console shows the result
But I have searched for a way to retrieve longitude/latitude pairs,
The first pair should be
latitude 109.987
longitude 102.987
- but so far without luck - help would really be appreciated :)
I think you should restructure your database to something like this:
Vandreture {
jsonFile {
length: "16.2"
link: "www.second.com"
navn: "Kagerup rundt"
positions {
-KqXukxnw3mL38oPeI4y {
x: "102.987"
y: "109.987"
}
-KqXukxnw3mL38oPeI5- {
x: "108.234"
y: "99.098"
}
}
}
}
Then getting your coordinates would be far easier!
Get your "position" as a dictionary (aka [String: AnyObject]).
Then get array of latitude and longitude. Then map of each element from latitude and longitude into a tuple or something like that.
guard let position = dict!["position"] as? [String: AnyObject],
let latitudeArray = position["latitude"] as? [String],
let longitudeArray = position["longitude"] as? [String] else { return }
print(latitudeArray)
print(longitudeArray)
thanks Victor Apeland - now I changed the structure of the database. I was not able to do it exactly as you suggested!!
I concatenated the lon/lat as a string (not the best - i know - I'm a newbie to swift, and firebase is not well documented)
to retrieve the individual elements from my long/lat, I used this method, and I was particular interested in the first reading, so I made a list, populated it, and got the first element, parsing it to a new method
func responsePosition() {
var positions = [String]()
ref?.child("Vandreture").child(fileName).child("position").observe(.value, with: { (snapshot) in
for snap in snapshot.children {
let userSnap = snap as! FIRDataSnapshot
let uid = userSnap.key //the uid of each user
let userDict = userSnap.value as! String
positions.append(userDict)
}
let pos : String = positions[0]
self.formatPositions(pos : pos)
})
}
In my firebase lon-lat was divided by a space, so I split the string and cast to double.
func formatPositions(pos : String) {
let lotLangDivided = pos.components(separatedBy: " ")
let longitude = Double(lotLangDivided[0])
let latitude = Double(lotLangDivided[1])
}
now I got the result i wanted
- thanks for the help Victor and Khuong :)
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.
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!)
}
}
}
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
}
..
}
}