Get child of dictionary with unknown parent key - swift

I have a dictionary from type [String: Any] that looks like this
"arExtensions" : {
"images" : {
"-LmgO2yG_TWbfOM4Y8X3" : {
"imagePath" : "https://firebasestorage.googleapis.com/v0/b/gpk-citycards.appspot.com/o/ARResources%2F78E88F6D-52F0-43A3-B585-9760D19F0B81?alt=media&token=be3a664f-a94b-4ead-bea4-1197155c016e",
"position" : "bottom"
},
"-LmgO4qaMKupHZIAEoLk" : {
"imagePath" : "https://firebasestorage.googleapis.com/v0/b/gpk-citycards.appspot.com/o/ARResources%2FC7303CF9-0E86-4DC6-A5F5-2761537F0A30?alt=media&token=1f928774-8221-474b-881e-7f395e439131",
"position" : "rightMiddle"
},
"-LmgO9vLT8rEx9Ndog4S" : {
"imagePath" : "https://firebasestorage.googleapis.com/v0/b/gpk-citycards.appspot.com/o/ARResources%2F9132B5B6-E904-4BCE-B56A-0271CB901A7D?alt=media&token=bd66cd1d-494b-4a82-8d74-6e00ac8c8ae6",
"position" : "leftMiddle"
}
}
}
I want to add the images into an object.
var imageObjects: [ImageObject] = []
I know that I can get the values with the keys like this
let dictImages: [String: Any] = dictArExtensions["images"] as! [String : Any]
unfortunately I don't know the key of the children of images.
with two iteration, first to get the key, second to get the needed values i solved the problem like this
var imageKeys: [String] = []
for dict in dictImages{
print(dict.key)
imageKeys.append(dict.key)
print(dict.value)
}
for keys in imageKeys{
let dictImage: [String: Any] = dictImages[keys] as! [String : Any]
let imagePath = dictImage["imagePath"] as? String
let position = dictImage["position"] as? String
imageObjects.append(ImageObject(imagePath: imagePath!, position: position!))
}
but, it seems like a bad solution.
Is there a better or rather more professional solution to this?

You can enumerate the dictionary very simply with key and value but the key is actually unused.
And if you declare the child dictionary as [String:String] you can get rid of the type cast
for (_, value) in dictImages {
let dictImage = value as! [String:String]
let imagePath = dictImage["imagePath"]
let position = dictImage["position"]
imageObjects.append(ImageObject(imagePath: imagePath!, position: position!))
}
Please read the section about dictionaries in the Language Guide

Related

How to store custom object in Firebase with Swift?

I'm porting an android app and using firebase in android it is possible to save a format in this way.
How can i do this on Swift? I read that i can store only this kind of data
NSString
NSNumber
NSDictionary
NSArray
How can I store the obj in atomic operation?
It's correct to store every field of the user object in separate action?
Firebase on Android
mDatabaseReferences.child("users").child(user.getUuid()).setValue(user)
I generally store objects as dictionaries on firebase. If, within my application, I have a User object, and it has properties as such:
class User {
var username = ""
var email = ""
var userID = ""
var consecutiveDaysLoggedOn = Int()
}
let newUser = User()
newUser.username = "LeviYoder"
newUser.email = "LeviYoder#LeviYoder.com"
newUser.userID = "L735F802847A-"
newUser.consecutiveDaysLoggedOn = 1
I would just store those properties as a dictionary, and write that dictionary to my firebase database:
let userInfoDictionary = ["username" : newUser.username
"email" : newUser.email
"userID" : newUser.userID
"consecutiveDaysLoggedOn" : newUser.consecutiveDaysLoggedOn]
let ref = Database.database().reference.child("UserInfo").child("SpecificUserFolder")
// ref.setValue(userInfoDictionary) { (error:Error?, ref:DatabaseReference) in
ref.setValue(userInfoDictionary, withCompletionBlock: { err, ref in
if let error = err {
print("userInfoDictionary was not saved: \(error.localizedDescription)")
} else {
print("userInfoDictionary saved successfully!")
}
}
Does that address your question?
I come with a little extension used to convert a standard SwiftObject in readable dictionnary for FireStore:
extension Encodable {
var toDictionnary: [String : Any]? {
guard let data = try? JSONEncoder().encode(self) else {
return nil
}
return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
}
}
For example, in my model:
struct Order: Encodable {
let symbol: String
let shares: Int
let price: Float
let userID: String
}
Called with this line:
let dictionnary = order.toDictionnary
This is the kind of dictionary generated
4 elements
▿ 0 : 2 elements
- key : "symbol"
- value : FP.PAR
▿ 1 : 2 elements
- key : "shares"
- value : 10
▿ 2 : 2 elements
- key : "userID"
- value : fake_id
▿ 3 : 2 elements
- key : "price"
- value : 100
Use
self.ref.child("users").child(user.uid).setValue(["username": username])

create custom nested tableview cells

I want to create a nested comment section. I am using Firebase as my database. In my app I have a comment section on each post. Logged in users have the ability to comment on a post and their comments can also be commented on, creating a nested effect.
So first I display the comments that were made to the original post. What I want to do is to go through each comment and check to see if there is a comment for that comment and if there is a comment, I want it to display directly under that comment. Just like Instagram or Facebook.
Here is a JSON example of what a nested comment would look like in Firebase
{
"author" : "patient0",
"comments" : {
"comment-487" : {
"author" : "Doctor1",
"comments" : {
"comment-489" : {
"content" : "Your internal capsule in your cerebrum was affected by the stroke",
"id" : "comment-489",
"reply_to" : "comment-487",
"reply_to_type" : "comment"
},
"comment-490" : {
"author" : "Doctor2",
"content" : "Your internal capsule is closely associated with your basal ganglia structures",
"id" : "comment-490",
"reply_to" : "comment-487",
"reply_to_type" : "comment"
}
},
"content" : "I recently had a stroke",
"id" : "comment-487",
"post_id" : "post-1069",
"reply_to" : "post-1069",
"reply_to_type" : "post"
},
"comment-491" : {
"author" : "MedStudent",
"comments" : {
"c_1531642274921" : {
"content" : "Wow! I wonder what cranial nerves were affected due to the hemorrhage",
"id" : "c_1531642274921",
"post_id" : "post-1069",
"pub_time" : 1531642274922,
"reply_to" : "comment-491",
"reply_to_type" : "comment"
}
},
"content" : "The hemorrhage was by the pons and cranial nerve 3 is by the pons, maybe the patient lost the ability to accommodate their eye sight and keep their eyes open.",
"id" : "comment-491",
"num_likes" : 0,
"post_id" : "post-1069",
"reply_to" : "post-1069",
"reply_to_type" : "post"
}
},
"content" : "I have a headache",
"id" : "post-1069",
"num_comments" : 5,
"title" : "I have a headache, should I go to the hospital",
}
As of now I am able to get the inital comments to print (the comments made directly to the post)
func loadComments(){
Database.database().reference().child("main").child("posts").child(postID!).child("comments").queryOrdered(byChild: "id").observeSingleEvent(of: .value, with: { (snapshot:DataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for testingkey in postsDictionary.keys {
Database.database().reference().child("main").child("posts").child(self.postID!).child("comments").child(testingkey).child("comments").queryOrdered(byChild: "post_id").observeSingleEvent(of: .value, with: { (snapshot:DataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
}
DispatchQueue.main.async(execute: {
self.TableView.reloadData()
})
}
})
}
for post in postsDictionary {
//main comments
self.Comments.add(post.value)
}
DispatchQueue.main.async(execute: {
self.TableView.reloadData()
})
}
})
}
I just don't know how to go through each post to check to see if there is a comment associated with it. Also if there is a comment associated with the original comment, I want it to print out in a custom cell.
I'd create a class or struct for comment, with an array of comments as a property.
class Comment {
let id: String
let author: String
var content: String
var comments: [Comment]
}
Then I'd create a TopLevelComment class as a subclass of Comment
class TopLevelComment: Comment {
// Whatever special properties you want your top level comments to have
}
You can now check if a comment is replying to a post or a comment by simply using
comment is TopLevelComment
Then you should restructure your database appropriately so you can cast it to the Comment class
For your tableView, I'd use a tableview for each top level comment, maybe even a section for each.
You can create an element for comment elements .
var commentElements = [CustomStruct]()
After creating Custom Element pull the variables from Firebase and save .
if let postsDictionary = snapshot .value as? [String: AnyObject] {
guard let comment = postsDictionary["comments"] as? NSArray else { return }
for com in comment {
guard let commentObject = com as? [String:Any] else { return }
let id = commentObject["id"]
let type = commentObject["reply_to_type"]
let replyTo = commentObject["reply_to"]
let content = commentObject["content"]
let element = CustomStruct(id:id , type:type , ....)
commentElements.append(element)
}
for post in postsDictionary {
}
DispatchQueue.main.async(execute: {
self.TableView.reloadData()
})
}
After pulling all elements , you may group based on comment Id . And you can show with Section in TableView.You sort the first element "reply_to_type" : "post"
As per your question, I believe that you are having difficulty in figuring out how to parse the JSON to a format (or view model) which can be used to display your posts and comments.
You can use the following model sample (with reworks or tweaks of your own, if needed) to parse and organize your posts and it's comments.
class Post {
var author: String?
var comments: [Post] = []
var content: String?
var id: String?
var numComments: Int?
var title: String?
init(dict: [String: AnyObject]?) {
author = dict?["author"] as? String
content = dict?["content"] as? String
id = dict?["id"] as? String
numComments = dict?["num_comments"] as? Int
title = dict?["title"] as? String
if let commentsDict = dict?["comments"] as? [String: AnyObject] {
// Sort the comments based on the id which seems to be appended to the comment key.
let commentIds = commentsDict.keys.sorted()
for id in commentIds {
let comment = commentsDict[id] as? [String : AnyObject]
comments.append(Post(dict: comment))
}
}
}
}
Usage:
//
// postDict is your dictionary object corresponding to one post.
//
// Assign your post's dictionary item to this variable.
//
var postDict: [String: AnyObject]?
// "Post" object which has recursive comments within up to any number of levels.
// Comments are also using the same model object.
// If you want to use another, you can create one with the corresponding elements.
let post = Post(dict: postDict)
P.S: The JSON structure looks to be not of a unique structure. You might want to rework on this structure to make sure that your content gets mapped neatly.

How to retrieve a value from dictionary in Swift 3

I have this function that fetch users from FireBase and convert them in Dictionary:
let leaderBoardDB = FIRDatabase.database().reference().child("scores1").queryOrderedByValue().queryLimited(toLast: 5)
leaderBoardDB.observe( .value, with: { (snapshot) in
print("scores scores", snapshot)
if let dictionary = snapshot.value as? [String: Any] {
for playa in dictionary {
let player = Player()
print("plaaaaayyyyyaaaaa", playa)
print("plaaaaayyyyyaaaaa key", playa.key)
print("plaaaaayyyyyaaaaa.value", playa.value)
player.id = playa.key
print(playa.key["name"])
}
}
}, withCancel: nil)
}
and I get this result:
plaaaaayyyyyaaaaa ("inovoID", {
name = Tatiana;
points = 6; }) plaaaaayyyyyaaaaa key inovoID plaaaaayyyyyaaaaa.value {
name = Tatiana;
points = 6; } aaaa i id Optional("inovoID")
the problem is that i can't obtain the name and the points of the user. when i try it with:
print(playa.key["name"])
it gaves me this error:
Cannot subscript a value of type 'String' with an index of type 'String'
can anyone help me with this, please?
Since your JSON is
"inovoID" : { "name" : "Tatiana", "points" : 6 }
playa.key is "inovoID"
playa.value is { "name" : "Tatiana", "points" : 6 }
The key is String and cannot be subscripted. That's what the error says.
You need to subscribe the value and safely cast the type to help the compiler.
if let person = playa.value as? [String:Any] {
print(person["name"] as! String)
}
I think you're looking for:
player.name = dictionary["name"] as? String
You don't need to iterate through the dictionary to access it's values. If you're looking for the value of a key, just get it.

Dictionary reference not updating dictionary

I have a reference to a dictionary and when I debug the code I can see that the reference is being populated but the actual dictionary is not getting any of the values.
var newItems = [String:Dictionary<String,AnyObject>]()
for item in self.items{
let itemRef = ref.child("items").childByAutoId()
//let itemOptions: NSMutableArray = []
//let itemRemovedOptions: NSMutableArray = []
//let itemDiscounts: NSMutableArray = []
newItems.updateValue([String:AnyObject](), forKey: "\(itemRef.key)")
newItems["\(itemRef.key)"]?.updateValue(item.parentCategory.id, forKey: "parentCategoryId")
newItems["\(itemRef.key)"]?.updateValue(item.item.id, forKey: "itemId")
newItems["\(itemRef.key)"]?.updateValue(item.item.name, forKey: "name")
newItems["\(itemRef.key)"]?.updateValue(item.qty, forKey: "qty")
newItems["\(itemRef.key)"]?.updateValue(item.discountAmount, forKey: "discountAmount")
newItems["\(itemRef.key)"]?.updateValue(item.notes, forKey: "notes")
newItems["\(itemRef.key)"]?.updateValue(item.price, forKey: "price")
//set options
newItems["\(itemRef.key)"]?.updateValue([String:Dictionary<String,AnyObject>](), forKey: "options")
for option in item.options{
if var optionsRef = newItems["\(itemRef.key)"]?["options"] as? [String:Dictionary<String,AnyObject>]{
optionsRef.updateValue([String:AnyObject](), forKey: "\(option.item.id)")
optionsRef["\(option.item.id)"]?.updateValue(option.item.name, forKey: "name")
optionsRef["\(option.item.id)"]?.updateValue(option.group.id, forKey: "group")
optionsRef["\(option.item.id)"]?.updateValue(option.qty, forKey: "qty")
optionsRef["\(option.item.id)"]?.updateValue(option.price, forKey: "price")
}
}
}
The primary issue here is that when you say if var optionsRef = newItems["\(itemRef.key)"]?["options"] as? [String:Dictionary<String,AnyObject>], you're making a copy of the Dictionary, since Dictionaries are value types in Swift. As a result, all subsequent modifications of the dictionary are applied only to your local copy, and not the one you intended. To alleviate this, you must assign the modified version of the dictionary back into the data structure where you wanted it changed.
But in addition to that primary problem, there are many other, softer, issues with this code. There's a reason this question over 50 views and no answers. People see this, say "Nope!" and run away screaming. Here are some key points:
Be consistent with your use of shorthand notation. [String : [String : AnyObject], not [String : Dictionary<String, AnyObject>].
Don't use string interpolation ("\(foo)") solely to convert types to strings. If the values are already strings, use them directly. If they're not, convert them with String(foo).
Don't use updateValue(_:forKey:). There's pretty much never a reason. Prefer subscript syntax.
When initializing a complex structure (such as your dictionary), initialize it first, then add it. Don't add it, then repeatedly access it through cumbersome copy/pasting.
In your case, consider newItems["\(itemRef.key)"]?.updateValue(.... With every line, the key must be hashed, and used to retrieve the value from the newItems dictionary. Then, the returned value must be checked for nil (by the ?. operator), doing nothing if nil, otherwise executing a method. You know the value won't be nil because you set it, but it has to do all this checking anyway.
Prefer Dictionary literals over mutation wherever possible.
Don't use Foundation data structures (NSArray, NSDictionary, etc.) it's absolutely necessary.
Here's my take on this code. It might need some touch ups, but the idea is there:
var newItems = [String : [String : AnyObject]() //fixed inconsistent application of shorthand syntax
let itemRef = ref.child("items").childByAutoId() //this shouldn't be in the loop if it doesn't change between iterations
for item in self.items {
// There's pretty much no reason to ever use updateValue(_:forKey:), when you can just use
// subscript syntax. In this case, a Dictionary literal is more appropriate.
var optionsDict = [String : [String : AnyObject]();
for option in item.options {
optionsDict[option.item.id] = [
"name" : option.item.name,
"group" : option.group.id,
"qty" : option.qty,
"price" : option.price,
]
}
newItems[itemRef.key] = [
"parentCategoryId" : item.parentCategory.id,
"itemId" : item.item.id,
"name" : item.item.name,
"qty" : item.qty,
"discountAmount" : item.discountAmount,
"notes" : item.notes,
"price" : item.price,
"options" : optionsDict
]
}
Here's an alternative that avoids having to define optionsDict separately:
let itemRef = ref.child("items").childByAutoId()
for item in self.items {
newItems[itemRef.key] = [
"parentCategoryId" : item.parentCategory.id,
"itemId" : item.item.id,
"name" : item.item.name,
"qty" : item.qty,
"discountAmount" : item.discountAmount,
"notes" : item.notes,
"price" : item.price,
"options" : item.options.reduce([String : AnyObject]()) { (var dict, option) in
optionsDict[option.item.id] = [
"name" : option.item.name,
"group" : option.group.id,
"qty" : option.qty,
"price" : option.price,
]
}
]
}

Swift: '(String) -> AnyObject?' does not have a member named 'subscript'

I am trying to convert a PFUser (from Parse.com) to a dictionary of [String : AnyObject] type, but I got the following error that seems very confusing to me. I did google search and found the following two SO questions, which, however, still not helpful in solving my problem:
Error:
'(String) -> AnyObject?' does not have a member named 'subscript'
Found these two posts:
relevant SO post 1,
relevant SO post 2
My PFUser has the keys: fullName, email, latitude, longitude, and linkedInUrl.
Since the types of the values for the above keys are a mix of String and Double. so I am trying to create the following Dictionary:
var userData : [String: AnyObject] = [
"fullName" : pfUser.valueForKey("fullName") as? String,
"latitude" : pfUser.valueForKey("latitude") as? Double,
"longitude" : pfUser.valueForKey("longitude") as? Double,
"email" : pfUser.valueForKey("email") as? String,
"linkedInUrl" : pfUser.valueForKey["linkedInUrl"] as? String
]
The version of my XCode is 6.4. I appreciate your help.
You are declaring wrong type of userData. It must be [String: AnyObject?] because your values are optionals.
And don't forget to change this line "linkedInUrl" : pfUser.valueForKey["linkedInUrl"] as? String with this line "linkedInUrl" : pfUser.valueForKey("linkedInUrl") as? String.
And your code will be:
var userData : [String: AnyObject?] = [
"fullName" : pfUser.valueForKey("fullName") as? String,
"latitude" : pfUser.valueForKey("latitude") as? Double,
"longitude" : pfUser.valueForKey("longitude") as? Double,
"email" : pfUser.valueForKey("email") as? String,
"linkedInUrl" : pfUser.valueForKey("linkedInUrl") as? String
]
There are two mistakes in your current code
First one is, use ( ) instead of using [ ] to get the value using the key in the following line.
"linkedInUrl" : pfUser.valueForKey["linkedInUrl"] as? String
Second one is, use an exclamation point instead to unwrap the optional.
Your code should look like what's shown below.
var userData : [String: AnyObject] = [
"fullName" : pfUser.valueForKey("fullName") as! String,
"latitude" : pfUser.valueForKey("latitude") as! Double,
"longitude" : pfUser.valueForKey("longitude") as! Double,
"email" : pfUser.valueForKey("email") as! String,
"linkedInUrl" : pfUser.valueForKey("linkedInUrl") as! String
]
The other solution is, making the dictionary declaration as [String: AnyObject?] to hold the optional values.