Firebase Retrieve Data - Could not cast value - ios

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!)
}
}
}

Related

How do I retrieve specific values from a firebase database and assign it to specific variables in Swift 4?

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 can't get the data from firebase database as a subclass object

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)

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.

Could not cast value of type '__NSCFNumber' (0x1b4520df0) to 'NSString'

let uid = FIRAuth.auth()?.currentUser?.uid
let ref = FIRDatabase.database().reference().child("users").child((uid)!)
ref.observeSingleEvent(of: .value, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
let htest = dictionary["h"] as! String //error is mainly here signal SIGABRT
var inthtest = Int(htest)
if (inthtest==0){
self.checkPointsk()
print("scanning1")
I've tried changing the as String Value To int Value but it still doesn't work
The error is self explanatory. You're trying to cast a number as a string. Replace the below two lines
let htest = dictionary["h"] as! String
var inthtest = Int(htest)
with
var inthtest = dictionary["h"] as! Int
The best way to avoid crashes like this is to use conditional binding.
if var inthtest = dictionary["h"] as? Int{
//Do your stuff
}
else if var inthtest = dictionary["h"] as? String{
if let integerValue = Int(inthtest){
//Do your stuff
}
}

Populating tableView with data obtained from Firebase

Here is my scenario:
1) datasource to my tableView is an array called books - var books:[BookItem]
2) I populate books anytime some data in db has changed
3) I create BookItem by joining data from two tables, since BookItem consists of some user-specific data and some general data about the book itself.
I wrote a method called createDataSource that takes care of populating books array with BookItems
func createDataSource()
{
self.books.removeAll()
let userId:String = UserDefaults.standard.value(forKey: "UserId") as! String
let bookForUserRef = self.ref!.child("users").child(userId).child("userbooks")
bookForUserRef.observe(.value, with: { snapshot in
var i = 0
for element in snapshot.children
{
i = i + 1
let item:FIRDataSnapshot = element as! FIRDataSnapshot
let postDict = item.value as! [String : AnyObject]
let key = item.key
let booksRef = self.ref!.child("books").child(key)
booksRef.observe(.value, with: { snapshot in
let bookItem = (snapshot as! FIRDataSnapshot).value as! [String : AnyObject]
let id = booksRef.key
print(id)
let record = [bookItem["title"] as! String, bookItem["author"] as! String, "0", "\(bookItem["pagesCount"] as! Int)"]
let item = BookItem(title: bookItem["title"] as! String, author: bookItem["author"] as! String, pagesCount: bookItem["pagesCount"] as! Int, currentPage: postDict["currentPage"] as! Int, language: bookItem["language"] as! String, year: nil, ISBN: nil, id: id, added: postDict["added"] as? String ?? "21")
self.books.append(item)
self.tableView.reloadData()
})
}
})
}
Anytime I call this method, all existing elements gets duplicated in books array.
I thought it might have something to do with for loop, but after debugging it seems alright.
I suppose there is something specific with observers in firebase that duplicates the elements. I am rather new to firebase and I suppose my approach might be a bit messed and confusing.
What should be modified in the createDataSource() method, to get rid of elements duplication?
Also, is my approach to working with firebase and tableView correct?
Thanks to #ntoonio comment I got it solved
func createDataSource()
{
let userId:String = UserDefaults.standard.value(forKey: "UserId") as! String
let bookForUserRef = self.ref!.child("users").child(userId).child("userbooks")
bookForUserRef.observe(.value, with: { snapshot in
var i = 0
self.books.removeAll()
for element in snapshot.children
{
i = i + 1
let item:FIRDataSnapshot = element as! FIRDataSnapshot
let postDict = item.value as! [String : AnyObject]
let key = item.key
let booksRef = self.ref!.child("books").child(key)
booksRef.observe(.value, with: { snapshot in
let bookItem = (snapshot as! FIRDataSnapshot).value as! [String : AnyObject]
let id = booksRef.key
print(id)
let record = [bookItem["title"] as! String, bookItem["author"] as! String, "0", "\(bookItem["pagesCount"] as! Int)"]
let item = BookItem(title: bookItem["title"] as! String, author: bookItem["author"] as! String, pagesCount: bookItem["pagesCount"] as! Int, currentPage: postDict["currentPage"] as! Int, language: bookItem["language"] as! String, year: nil, ISBN: nil, id: id, added: postDict["added"] as? String ?? "21")
self.books.append(item)
self.tableView.reloadData()
})
}
})
}

Resources