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.
Related
I am trying to iterate over the users' information where and save it in an Object.
Here is my data
{
"users" : {
"ApC2wS444YbEEUt5BOpFjkn7YTD3" : {
"UserInfo" : {
"lat" : 123123,
"long" : 0,
"petAge" : 5,
"petNeme" : "zhshs"
}
},
"doRJjseSogNJrCQ55zMGlJwj6jh2" : {
"UserInfo" : {
"UID" : "doRJjseSogNJrCQ55zMGlJwj6jh2",
"lat" : 0,
"long" : 0,
"petAge" : 5,
"petName" : "BBB"
}
}
}
}
My code is like this
ref = Database.database().reference()
tableView.delegate = self
tableView.dataSource = self
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference()
self.ref.child("users").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
let value = snapshot.value as! NSDictionary
let userInfo = value[0] as! NSDictionary
let UID = userInfo["UID"] as! String
let lat = userInfo["lat"] as! Double
let long = userInfo["long"] as! Double
let name = userInfo["petName"] as! String ?? ""
let age = userInfo["petAge"] as! Double
}
}) { (error) in
print(error.localizedDescription)
}
I don't really understand how I should handle snapshots and how to get the data properly
I was able to a fetch a single node but when I try to pass iterate, it brings me back the same node with every iteration
Any help?
In your example you use in a for loop this let userInfo = value[0] as! NSDictionary which later gives you only the first node: [0].
Also let me show you my example of retrieving data from a snapshot (in my case I have cards with autoId):
func loadDataFromDb(completion: #escaping ([Card])->()) {
var cards: [Card] = []
let userRef = getCurrentUserRef()
usersRef.child(userRef).child(Paths.cards).observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let card = Card(snapshot: snapshot) {
cards.append(card)
}
}
completion(cards)
}
}
Also I have failable init in Card:
private enum Constants {
static let name = "name"
static let barcode = "barcode"
}
final class Card {
let name: String
let barcode: String
init(_ name: String, barcode: String) {
self.name = name
self.barcode = barcode
}
init?(snapshot: DataSnapshot) {
guard let value = snapshot.value as? [String : AnyObject],
let name = value[Constants.name] as? String,
let barcode = value[Constants.barcode] as? String else {
return nil
}
self.name = name
self.barcode = barcode
}
}
I have an issue with my least favourite part in Firebase. I want to pull a post from user's following list (every user has one and only one post). First, I created a completion handler to get a list of all followers from Firebase and store it in userArray array of strings:
func GetUsersInFollowing(completion: #escaping (Bool) -> ()) {
ref.child("following").queryOrdered(byChild: FIRAuth.auth()!.currentUser!.uid).observeSingleEvent(of: .value, with: { (snapshot) in
for group in snapshot.children {
self.userArray.append((group as AnyObject).key)
}
completion(true)
})
}
Now the plan is to pull a post from every element of userArray.
Here is where the problem starts. I call CreatePosts() immediately after GetUsersInFollowing() completes.
func CreatePosts() {
for x in userArray {
var thePost = Post()
print("1")
self.ref.child("users").child(x).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.fullName = value?["fullname"] as? String ?? ""
thePost.username = value?["username"] as? String ?? ""
thePost.profileImageURL = value?["photourl"] as? String ?? ""
print("2")
})
self.ref.child("posts").child(x).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.description = value?["description"] as? String ?? ""
thePost.info = value?["location"] as? String ?? ""
thePost.postImageURL = value?["photoURL"] as? String ?? ""
thePost.timePost = value?["timestamp"] as? NSDate
thePost.upVotes = value?["upvotes"] as? Int ?? 0
})
self.postArray.append(thePost)
self.tableView.reloadData()
}
}
Everything looks ok to me, but it surely isn't. Here's how I create cells:
func configureCell(post: Post) {
self.post = post
self.username.text = post.username
self.profileImage = post.profileImageURL
print("3")
self.fullname.text = post.fullName
self.timestamp.text = post.timePost
self.upvotes.text = post.upVotes
self.location.text = post.location
self.descriptionText.text = post.description
}
The output in the console varies, but usually I get:
1
1
3
3
2
2
The idea is to first retrieve all data from Firebase, add it to post object, append the object to the array and then create cell for that object with data downloaded. Cell is already created even though data is not retrieved. I think that is the problem. Thank you, every suggestion is appreciated.
You need to inner query for combining both user profile data and post data.
Like this -
func CreatePosts() {
//Using userPostArrayObjFetched as a counter to check the number of data fetched.
//Remove this code, if you don't want to wait till all the user data is fetched.
var userPostArrayObjFetched = 0
for (index,userID) in userArray.enumerated() {
print("1" + userID)
self.ref.child("users").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
var thePost = Post()
let value = snapshot.value as? NSDictionary
thePost.fullName = value?["fullname"] as? String ?? ""
thePost.username = value?["username"] as? String ?? ""
thePost.profileImageURL = value?["photourl"] as? String ?? ""
print("2" + userID)
self.ref.child("posts").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.description = value?["description"] as? String ?? ""
thePost.info = value?["location"] as? String ?? ""
thePost.postImageURL = value?["photoURL"] as? String ?? ""
thePost.timePost = value?["timestamp"] as? NSDate
thePost.upVotes = value?["upvotes"] as? Int ?? 0
print("3" + userID)
self.postArray.append(thePost)
// Uncomment if you want to reload data as fetched from Firebase without waiting for all the data to be fetched.
// self.tableView.reloadData()
userPostArrayObjFetched += 1
if userPostArrayObjFetched == userArray.count{
self.tableView.reloadData()
}
})
})
}
}
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.
I am trying to print array from the firebase. Actually if we tap a medication in a list(tableviewcontroller), it will show its specfic dosages. I got stucked to retrieve the dosages list. Here is my code to get data from firebase. Any help is appreciated. Thanks in advance. My firebase structure looks like this.. firebase img
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observeEventType(.Value, withBlock: { snapshot in
for item in snapshot.children{
FIRDatabase.database().reference().child("medication").child("options").observeEventType(.Value, withBlock: {snapshot in
print(snapshot.value)
})
}
})
You should take a look on firebase documentation https://firebase.google.com/docs/database/ios/read-and-write
but if I'm understanding your idea, you probably has a model class for your medications. So, to retrieve your data you should do like this for Swift 3.0:
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observe(.value, with: { (snapshot) in
for item in snapshot.children{
// here you have the objects that contains your medications
let value = item.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let dossage = value?["dossage"] as? String ?? ""
let type = value?["type"] as? String ?? ""
let options = value?["options"] as? [String] ?? ""
let medication = Medication(name: name, dossage: dossage, type: type, options: options)
// now you populate your medications array
yourArrayOfMedications.append(medication)
}
yourTableView.reloadData()
})
}
Now that you have your array with all your medications, you just need to populate your tableView with this medications. When someone press an item on table you can just call prepareForSegue: and send your yourArrayOfMedications[indexPath.row].options to the next view
The solution is same as above but with a small change.
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observe(.value, with: { (snapshot) in
for item in snapshot.children{
// here you have the objects that contains your medications
let value = item.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let dossage = value?["dossage"] as? String ?? ""
let type = value?["type"] as? String ?? ""
let options = value?["options"] as? [String : String] ?? [:]
print(options["first"]) // -> this will print 100 as per your image
// Similarly you can add do whatever you want with this data
let medication = Medication(name: name, dossage: dossage, type: type, options: options)
// now you populate your medications array
yourArrayOfMedications.append(medication)
}
yourTableView.reloadData()
})
}
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!)
}
}
}