I try to parse and assign data, which I am becoming from Firebase
The structure in Firebase looks like this:
I try to fetch data from database and assign it to instance of class Meal:
ref = Database.database().reference()
databaseHandle = ref.child("Meal").observe(.value, with: { (snapshot) in
var downloadedName : String!
var downloadedPhoto : String!
var downloadedRating : Int!
var downloadedSteps : Array <String>!
var downloadedIngredients : [Dictionary <String, String>]!
print(snapshot.value)
if let dict = snapshot.value as? Dictionary<String, Any>{
print("VALUES!!!")
for key in dict.keys {
if let values = dict[key] as? Dictionary<String, Any> {
print(values)
if let name = values["name"] as? String{
downloadedName = name
}
if let photo = values["photo"] as? String{
downloadedPhoto = photo
}
if let rating = values["rating"] as? Int{
downloadedRating = rating
}
if let steps = values["steps"] as? Array<String>{
downloadedSteps = steps
}
if let ingredients = values["ingredients"] as? [Dictionary <String, String>]{
downloadedIngredients = ingredients
}
let meal = Meal(name: downloadedName, photo: UIImage(named: downloadedPhoto), rating: downloadedRating, steps: downloadedSteps, ingredients: downloadedIngredients)
self.meals.append(meal!);
}
}
The Meal class itself looks like this:
class Meal {
var name: String
var photo: UIImage?
var rating: Int
var steps: Array<String>
var ingredients: [Dictionary<String, String>]}
I get the first print - the whole data, so the connection with DB is OK, but as i try to assign it - nothing happens, no errors, no data (the second print with message VALUES!!! is not shown at all, what am I doing wrong?
Here is also what I get by first print
Optional(<__NSArrayM 0x600002fdaa60>(
{
ingredients = (
{
amount = 100;
ingredient = milk;
measurement = ml;
},
{
amount = 120;
ingredient = milk;
measurement = ml;
}
);
name = "Caprese Salad";
photo = meal1;
rating = 4;
steps = (
test1,
test11
);
},
{
ingredients = (
{
amount = 100;
ingredient = milk;
measurement = ml;
},
{
amount = 120;
ingredient = milk;
measurement = ml;
}
);
name = "Chicken and Potatoes";
photo = meal2;
rating = 3;
steps = (
test2,
test22
);
},
{
ingredients = (
{
amount = 100;
ingredient = milk;
measurement = ml;
},
{
amount = 120;
ingredient = milk;
measurement = ml;
}
);
name = "Pasta with Meatballs";
photo = meal3;
rating = 2;
steps = (
test3,
test33
);
}
)
)
So, I assume, I retrieve the data in the false way at some point, how could i fix it?
You have:
print(snapshot.value)
if let dict = snapshot.value as? Dictionary<String, Any>{
print("VALUES!!!")
....
}
You say that print(snapshot.value) is called but not print("VALUES!!!").
Well, that means that snapshot.value or it isn't a Dictionary which keys are String objects and values are of type Any.
Now, let's see the output of snapshot.value:
Optional(<__NSArrayM 0x600002fdaa60> ...
NSArrayM => NSMutableArray, so snapshot.value is an Array, not a Dictionary. Of course then the as? Dictionary will fail!
So you need to treat it as an Array.
Quickly written:
if let array = snapshot.value as? [[String: Any]] {
for aValue in array {
let name = aValue["name"] as? String ?? "unnamed"
let photoName = aValue["photo"] as? String ?? "noPhoto"
let rating = aValue["rating"] as? Int ?? 0
let steps = aValue["steps"] as? [String] ?? []
let ingredients = aValue["ingredients"] as? [[String: String]] ?? [:]
let meal = Meal(name: name, photo: UIImage(named: photoName), rating: rating, steps: steps, ingredients: ingredients)
self.meals.append(meal)
}
}
What could also been wrong with your approach:
var downloadedName : String!
loop {
if let name = values["name"] as? String {
downloadedName = name
}
let meal = Meal(name: downloadedName, ...)
}
Well, if for the second value you didn't have a name (either because it's not a String or because the value doesn't exist), you downloadedName would have the value of the first one, but the rest of the values of the second one.
I used the approach:
let name = aValue["name"] as? String ?? "unnamed"
Meaning, that if it's nil, it gets a default value.
But you could decide to accept only valid one, by a guard let or if let potentially on all the subvalues.
For instance:
if let name = aValue["name"] as? String,
let let photoName = aValue["photo"] as? String,
... {
let meal = Meal(name: name, photo: UIImage(named: photoName)
self.meals.append(meal)
}
And since your Meal.init() seems to return an optional, you could add it also in the if let and avoid the force unwrap of meal.
Related
My firebase data is as follows:
Matches
items
platinium
standard
-LQTnujHvgKsnW03Qa5s
code: "111"
date: "27/11/2018"
predictions
-0
prediction: "Maç Sonucu 1"
predictionRatio: "2"
startTime: "01:01"
I read this with the following code
databaseHandle = ref.observe(.childAdded, with: { (snapshot) in
if let matchDict = snapshot.value as? Dictionary<String, AnyObject> {
let m_key = snapshot.key
let m = Match(matchKey: m_key, matchData: matchDict)
self.matches.append(m)
}
self.matchesTableView.reloadData()
})
I have two datamodels
1 is match
2 is prediction
I can read code, date and starttime from database but with match object prediction data is not coming it says its nil, How can I get that data with match object?
You can set up the Model class as follows
class ListModel: NSObject {
var UID:String?
var Code:String?
var Date:String?
var PredictionsArr:[PredictionsObj]?
var StartTime:String?
}
class PredictionsObj : NSObject {
var Prediction : String?
var PredictionRatio : String?
}
In your ViewController you can add the below code
var ListArr = [ListModel]()
let ref = Database.database().reference().child("Matches").child(“items”).child(“standard”)
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let Obj = ListModel()
Obj.UID = snapshot.key
Obj.Code = dictionary["code"] as? String
Obj.Date = dictionary["date"] as? String
Obj.StartTime = dictionary["startTime"] as? String
let myPredictionsArr = dictionary["predictions"] as? NSArray
var myPredictionsObj = [PredictionsObj]()
if myPredictionsArr != nil{
for dict in myPredictionsArr as! [[String: AnyObject]] {
let detail = PredictionsObj()
detail.Prediction = dict["prediction"] as? String
detail.PredictionRatio = dict["predictionRatio"] as? String
myPredictionsObj.append(detail)
}
}
Obj.PredictionsArr = myPredictionsObj
self.ListArr.append(Obj)
self.ListTV.delegate = self
self.ListTV.dataSource = self
self.ListTV.reloadData()
}, withCancel: nil)
In this I am getting data from server response after posting parameters and here I need to display it on table view and it should be displayed like shown below in the image 0 is the price for the particular shipping method
already i had written model class for server response data and here it is
struct ShippingMethod {
let carrierCode : String
let priceInclTax : Int
let priceExclTax : Int
let available : Any
let carrierTitle : String
let baseAmount : Int
let methodTitle : String
let amount : Int
let methodCode : String
let errorMessage : Any
init(dict : [String:Any]) {
self.carrierCode = dict["carrier_code"] as! String
self.priceInclTax = dict["price_incl_tax"]! as! Int
self.priceExclTax = dict["price_excl_tax"]! as! Int
self.available = dict["available"]!
self.carrierTitle = dict["carrier_title"] as! String
self.baseAmount = dict["base_amount"]! as! Int
self.methodTitle = dict["method_title"]! as! String
self.amount = dict["amount"]! as! Int
self.methodCode = dict["method_code"] as! String
self.errorMessage = (dict["error_message"] != nil)
}
}
by using this I had formed an array type like this by using code
var finalDict = [String: [String]]()
var responseData = [ShippingMethod]()
do
{
let array = try JSONSerialization.jsonObject(with: data, options: []) as? [[String : Any]]
for item in array! {
self.responseData.append(ShippingMethod.init(dict: item))
}
print(self.responseData)
}
catch let error
{
print("json error:", error)
}
print(self.responseData)
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [String]()
}
self.finalDict[carrierTitle]!.append(methodTitle)
}
print(self.finalDict)
the output of this finalDict is ["Flat Rate": ["Fixed"], "Best Way": ["Table Rate"]] in this carrier title key value pair should be displayed as section title and is Flat Rate and method title key value pair should be displayed as rows in section Fixed but the problem is I need amount key value pair with it also for corresponding method title can anyone help me how to get this ?
Why don't you create another struct for displaying row data:
struct CarrierInfo {
let name:String
let amount:Int
}
Change your finalDict to
var finalDict = [String: [CarrierInfo]]()
and create CarrierInfo instance and set it in finalDict
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
let amount = dict.amount
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [CarrierInfo]()
}
self.finalDict[carrierTitle]!.append(CarrierInfo(name: carrierTitle, amount: amount))
}
Likewise you can make other required changes. This would neatly wrap your row display data inside a structure.
PS: I have not tested the code in IDE so it may contain typos.
You can assign another dictionary with key as methodTitle and amount as value. i.e., ["fixed":"whatever_amount"]
OR
You can use finalDict differently, like ["Flat Rate": ["tilte":"Fixed","amount":"0"], "Best Way": ["title":"Table Rate","amount":"0"]]
If it is difficult for you to code this, you can revert back.
Edit
You can use the following code to create the array in the second solution I suggested above:
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
let amount = dict.amount
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [[String:String]]()
}
let innerDict = ["title":methodTitle,"amount":amount]
self.finalDict[carrierTitle]!.append(innerDict)
}
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.
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!)
}
}
}
A long time since I have written iOS code but I have the following Model in an iOS app and works great but now we are finding out that detail is optional and we should allow nil values. How would I adjust the initializer to support this? Sorry, I find the optionals a bit difficult to grasp (concept makes sense - executing it is difficult).
class Item{
var id:Int
var header:String
var detail:String
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String,
let detail = dictionary["detail"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = detail
}
and creating:
var items = [Item]()
if let item = Item(dictionary: dictionary) {
self.items.append(item)
}
As in above answer by #AMomchilov, you could assign the value only if it exists in your init method.
But also you could check for the value and then access it like below:
class Item {
var id:Int
var header:String
var detail: String?
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = dictionary["detail"] as? String //if there is value then it will assign else nil will be assigned.
}
}
let dictionary = ["id": 10, "header": "HeaderValue"]
var items = [Item]()
if let item = Item(dictionary: dictionary) {
items.append(item)
print(item.id)
print(item.detail ?? "'detail' is nil for this item")
print(item.header)
}else{
print("No Item created!")
}
And the console is :
10
'detail' is nil for this item
HeaderValue
And if there is `detail' value present then:
let dictionary = ["id": 10, "header": "HeaderValue", "detail":"DetailValue"]
var items = [Item]()
if let item = Item(dictionary: dictionary) {
items.append(item)
print(item.id)
print(item.detail ?? "'detail' is nil for this item")
print(item.header)
}else{
print("No Item created!")
}
Console:
10
DetailValue
HeaderValue
Remove detail from the guard (as now a nil value is acceptable), and assign self.detail to dictionary["detail"] as? String.
class Item {
var id: Int
var header: String
var detail: String?
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = dictionary["detail"] as? String
}
Edit: Improved based on Santosh's answer.