Problem Accessing Items Within NSDictionary Swift - swift

I'm a newb to Swift programming, but experience in other languages.
I am having problem accessing items within NSDictionary to build out view elements. This is coming back from a Firebase instance.
Can someone take a look at the code and the output and lead me in the right direction to access these object properties?
ref.observe(.value, with: { (snapshot) in
for child in snapshot.children { //even though there is only 1 child
let snap = child as! DataSnapshot
let dict = snap.value as? NSDictionary
for (joke, item) in dict ?? [:] {
print(joke)
print(item)
}
}
})
This is the output from the print() methods.
joke2
{
PostUser = "Bobby D";
Punchline = "His money went to the movies.";
Rating = 1;
Setup = "Why did the dad go hungry?";
}
joke
{
PostUser = "Billy G";
Punchline = "Because he couldn't moo to a job.";
Rating = 3;
Setup = "Why did the cow go to school?";
}
Can someone tell me how to create items from these objects? Something like:
var posterName = joke.PostUser
When I try this, I get the error Value of type 'Any' has no member 'PostUser'. I've tried to access these DB object properties in multiple different ways described on SO and can't get any further.

I would recommend you to convert the output into objects like this:
struct Item {
var postUser: String?
var punchline: String?
var rating: Int?
var setup: String?
init(fromDict dict: [String: AnyObject] ) {
self.postUser = dict["PostUser"] as? String
self.punchline = dict["Punchline"] as? String
self.rating = dict["Rating"] as? Int
self.setup = dict["Setup"] as? String
}
}
And use it like this:
ref.observe(.value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
guard let dict = snap.value as? [String: AnyObject] else { continue }
let myItem = Item(fromDict: dict)
print(myItem)
}
})
But you could also access items in your dictionary directly like this:
let posterName = joke["PostUser"] as? String

Related

Not able to read data from Firebase realtime database

I have stored the comments under a post in firebase realtime database. The problem i have is that when i try to parse out the data from firebase i get an error that says Unexpectedly found nil while unwrapping an Optional value. So for example if i try to pront the data stored under degree, i get this nil error. But when i print "comments" instead of the "degree" i successfully fetch the data. My database structure looks like this.
func obeserveComments() {
// get auto-id of post
let commentKey = self.keyFound
let postRef = Database.database().reference().child("posts").child(commentKey)
var tempComments = [Comments]()
postRef.observe(.value, with: {(snapshot) in
if let dict = snapshot.value as? [String:Any] {
if let comments = dict["comments"] as? [String:Any] {
let degree = comments["reply degree"] as! String
// let name = comments["reply name"] as! String
// let text = comments["reply text"] as! String
// let university = comments["reply university"] as! String
// let photoURL = comments["reply url"] as! String
// let url = URL(string: photoURL)
// let timestamp = comments["timestamp"] as! Double
print(degree)
}
}
})
}
The answer by #aytroncb is a good answer, I prefer to leave Firebase data 'Firebasy' as long as possible. In other words coverting to dictionaries looses ordering and and find code like this
[String: [String: [String: Any]]]
To be very hard to read.
I prefer
let snap = snapshot.childSnapshot("comments") //snap becomes a DataSnapshot
So my solution maintains the order and leverages .childSnapshot to leave data in it's DataSnapshot form.
func readPostComments() {
let postRef = self.ref.child("posts") //self.ref points to my firebase
postRef.observeSingleEvent(of: .value, with: { snapshot in
let allPosts = snapshot.children.allObjects as! [DataSnapshot]
for postSnap in allPosts {
print("postId: \(postSnap.key)")
let commentsSnap = postSnap.childSnapshot(forPath: "comments") //will be a DataSnapshot
let allComments = commentsSnap.children.allObjects as! [DataSnapshot]
for commentSnap in allComments {
print(" commentId: \(commentSnap.key)")
let replyDegree = commentSnap.childSnapshot(forPath: "reply_degree").value as? String ?? "No Degree"
let replyName = commentSnap.childSnapshot(forPath: "reply_name").value as? String ?? "No Name"
print(" degree: \(replyDegree) by: \(replyName)")
}
}
})
}
EDIT
For a single post, remove the top part of the code that reads in and iterates over all posts.
func readCommentsForOnePost() {
let postRef = self.ref.child("posts")
let postCommentRef = postRef.child("post_0")
postCommentRef.observeSingleEvent(of: .value, with: { snapshot in
print("postId: \(snapshot.key)")
let commentsSnap = snapshot.childSnapshot(forPath: "comments") //will be a DataSnapshot
let allComments = commentsSnap.children.allObjects as! [DataSnapshot]
for commentSnap in allComments {
print(" commentId: \(commentSnap.key)")
let replyDegree = commentSnap.childSnapshot(forPath: "reply_degree").value as? String ?? "No Degree"
let replyName = commentSnap.childSnapshot(forPath: "reply_name").value as? String ?? "No Name"
print(" degree: \(replyDegree) by: \(replyName)")
}
})
}
Its because firebase is returning your data like this
{
"MAKFW244kdL)Cw;1": [Array of data],
"LOPSw!35pa3flAL4": [Array of data],
"ALV34VR4_A6Vn1a": [Array of data]
}
So change your initial casting of snapshot.value to this:
if let dict = snapshot.value as? [String: [String: Any]]
then loop through that new dictionary like this:
for objectJson in dict.values {
if let comments = objectJson["comments"] as? [String: [String: Any]] {
for commentJson in comments.values {
let degree = commentJson["reply_degree"] as? String
}
}
}
Update
Just read through your post again and noticed your trying to access the comments directly with a key, your first going to need to provide the PostId. Then you can use the above code to loop through the objects
let postRef = Database.database().reference().child("posts").child(postID)
alternatively I believe you can have the comments returned as a normal list by doing something like this:
let postRef = Database.database().reference().child("posts").child("\(postID)/{id}")

How to read all child data from firebase

How can i read all child data from Firebase.
let ShopRef = Database.database().reference(withPath: “ShoppingMallLst”).child(“ShoppingMall1”)
ShopRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
for data in snapshot.children.allObjects as! [DataSnapshot] {
if let data = data.value as? [String: Any] {
let Description = data["Description"] as? String
let Floor = data[“Floor”] as? Int
….
}
}
}
})
But how can i read the data from child "ShopPath?"
child "ShopPath" has type [String: [String: String]]
you can try:
let ShopPath = data["ShopPath"] as? [String: [String: String]]
The key to firebase is to keep data in snapshots as long as you can. So in this case instead of casting items to dictionaries, which loose ordering or arrays that get more complex as the structure gets deeper, leverage DataSnapshots.
Here's the main function to read all shops in ShoppingMall1 - similar to the code in your question.
func readMallShops() {
let ref = self.ref.child("ShoppingMallList").child("ShoppingMall1")
ref.observeSingleEvent(of: .value, with: { snapshot in
let allShopsSnap = snapshot.children.allObjects as! [DataSnapshot]
for shopSnap in allShopsSnap {
let shop = ShopClass(withSnap: shopSnap)
}
})
}
Then a class that holds data about each shop. Note that I pass each snapshot in to initialize the class
class ShopClass {
var name = ""
var height = ""
convenience init(withSnap: DataSnapshot) {
self.init()
let name = withSnap.childSnapshot(forPath: "ShopName").value as? String ?? "No Shop Name"
print("Shop: \(name)")
self.name = name
let shopPathSnap = withSnap.childSnapshot(forPath: "ShopPath")
let shopChildSnap = shopPathSnap.children.allObjects as! [DataSnapshot]
for childDataSnap in shopChildSnap { //iterate over the array in ShopPath
let height = childDataSnap.childSnapshot(forPath: "Height").value as! String
print(" height: \(height)")
self.height = height
}
}
}
And the output looks like this
Shop name: Test
height: 1,180
Shop name: Test 2
height: 2,000
I left off the other child nodes as if you can read height, you can read the rest. So this just assigns and prints out the shop name and height (as a string).
A suggestion as well. Arrays are not well suited for NoSql databases and their use is very situational (avoid if possible). If you're using an array, there's probably a better structure available.

Firebase Snapshot Isn't showing Data

Basically everything is working, except the showChild func is returning completion([]) because of the guard catData = Category(snapshot: catInfo). I am wondering why the guard let is returning completion. When I debug, catInfo does have 1 value as shown in my pic of database and I want to append catData.main to "cats". Below is code for the service method and Category model as well.
Firebase Database
static func showChild(completion: #escaping ([String]) -> Void) {
let ref = Database.database().reference().child("category").child(User.current.uid)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let snapshot = snapshot.children.allObjects as? [DataSnapshot] else {
return completion([])
}
var cats = [String]()
for catInfo in snapshot {
guard let catData = Category(snapshot: catInfo) else {
return completion([])
}
cats += catData.main
}
completion(cats)
})
}
import Foundation
import FirebaseDatabase.FIRDataSnapshot
class Category {
var key: String?
let main: [String]
init?(snapshot: DataSnapshot) {
guard !snapshot.key.isEmpty else {return nil}
if let dict = snapshot.value as? [String : Any]{
let main = dict["main"] as? [String]
self.key = snapshot.key
self.main = main ?? [""]
}
else{
return nil
}
}
}
The issue is pretty straightforward.
While your snapshot contains at least one node of data, it's not in a format that the Category init method understands. You're iterating over it's child nodes and in your screenshot, there's only one, with a key of 'main'
You are observing this node
fb_root
category
2ayHe...
and then you're iterating over it's child nodes which will be
main
0: Performance
so the key is 'main' and it's value is '0: Performance'
but your Category class is looking for a child node of 'main'
let main = dict["main"] as? [String]
There's not enough info to understand what will be contained in the rest of the structure so I can't tell you how to correct it, but at least you know what the problem is.
To clarify, this line
if let dict = snapshot.value as? [String : Any]
will make dict = [0: "Performance]

facing Issue in parsing in swift3

I am trying to parse the emergency data in into emergency struct but it never statifies the condition and get into else case.Here is my code and structure.Some thing i have written woring in first line.
if let emergencyDict = snapshotValue["emergency"] as? [String:[String:Any]]{
for (emerId, emerData) in emergencyDict {
let emer = Emergency.init(emergency: emerData as NSDictionary)
emergency.append(emer)
}
}
else{
let emer = Emergency.init(emerg: "" as AnyObject)
emergency.append(emer)
}
struct Emergency{
var emer_id: String
var emer_name: String
var emer_phoneNo: String
init(emergency: NSDictionary) {
if emergency.object(forKey: "id") != nil {
emer_id = emergency.object(forKey: "id") as! String
}
else{
emer_id = ""
}
}
}
The problem you are having emergency as Array with type [Any] and if you remove the first object then you get Array of type [[String:Any]]. So try like this way.
if let array = snapshotValue["emergency"] as? [Any],
let emergencyArrar = Array(array.dropFirst()) as? [[String:Any]] {
print(emergencyArray)
for emergency in emergencyArray {
print(emergency)
}
}
You have written wrong in this line:
if let emergencyDict = snapshotValue["emergency"] as? [String:[String:Any]]{
It should be:
if let emergencyDict = snapshotValue["emergency"] as? [[String:Any]]{
This question should belong to query from firebase database.
// you have to get the children in emergency,
// then get the value(dictionary) of each child
ref.child("emergency").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let keys = value?.allKeys // [1, 2, 3 ....]
for key in keys {
ref.child("emergency").child(key)..observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// Here is your dictionary
}
}
}) { (error) in
print(error.localizedDescription)
}

Retrieving data from Firebase with Swift 3.0 from a random child path

I'm trying to retrieve an array of items from a directory with a random folder using an existing array of strings as reference
My data looks like this:
Items
- RandomID
-title : "text"
-subtitle: "text"
So far this is what I have tried, but it isn't working:
var array = [String]() //array to use as reference
var returnedItems = [Item]() //array of item objects
func retrieveData()
{
for i in array
{
let ref = main.child("Items")
let query = ref.queryEqual(toValue: i)
query.observeSingleEvent(of: .value, with: { (snapshot) in
let item = Item!
if snapshot.hasChild("title")
{
item.title = (snapshot.value as! NSDictionary)["title"] as? String
}
if snapshot.hasChild("subtitle")
{
item.subtitle = (snapshot.value as! NSDictionary)["subtitle"] as? String
}
returnedItems.append(item)
self.tableView.reloadData()
print("Item: \(self.returnedItems.map { $0.title})")
})
}
}
Any help will be greatly appreciated!
Thanks in advance ;)
If you're trying to retrieve all children, you can just use a single listener (modeled after this example in the documentation):
var array = [String]() //array to use as reference
var returnedItems = [Item]() //array of item objects
func retrieveData() {
query.observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let item = Item!
if child.hasChild("title") {
item.title = (snapshot.value as! NSDictionary)["title"] as? String
}
if child.hasChild("subtitle") {
item.subtitle = (snapshot.value as! NSDictionary)["subtitle"] as? String
}
returnedItems.append(item)
self.tableView.reloadData()
print("Item: \(self.returnedItems.map { $0.title})")
})
}
// returnedItems will still be empty here, since the data hasn't
// been loaded yet. See the note after this code snippet.
}
But note that reading the data will still happen asynchronously here, so returnedItems will still be empty when retrieveData returns.