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,
]
}
]
}
Related
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
I have a data structure in firebase which I am showing that data on a tableview. I am getting the data from firebase. The tableview sections are hard coded like Motivation, Success etc... What is the best way to parse this data so that, when I add a new section in firebase console, it will add that section and data for the section on the tableview, without me hard coding the sections? Any help in the right direction would be appreciated, read the firebase doc but can't seem to figure it out.
Data Structure
{
"categories" : {
"motivation" : {
"one" : {
"name" : "Bob",
"title" : "Get up stand up"
},
"two" : {
"name" : "Arsitotle",
"title" : "Great philosopher"
}
},
"success" : {
"one" : {
"name" : "Les",
"title" : "You're great"
},
"three" : {
"name" : "Bob",
"title" : "One love"
},
"two" : {
"name" : "Wayne",
"title" : "You will be great"
}
}
}
}
** Retrieving the data**
ref.child("categories/motivation").observe(.childAdded, with: {(snapshot:DataSnapshot) in
if let values = snapshot.value as? [String:String] {
self.motivationDictionary.insert(values, at: 0)
}
})
}
ref.child("categories/success").observe(.childAdded, with: {(snapshot:DataSnapshot) in
if let values = snapshot.value as? [String:String] {
self.successDictionary.insert(values, at: 0)
}
})
}
I know this isn't the best way, but it works. Kind of redundant, but I am new to firebase and databases.
So you are having a fixed number of dictionaries where each dictionary has a name (motivationDictionary, successDictionary).
Instead you could have a dictionary of dictionaries (like in your data structure), such that the top dictionary contains categories, and under each key you have a dictionary of values for that category, i.e. the new self.categories["motivation"] is the same as the old self.motivationDictionary and so on.
This should work, but it is not the best practice to just operate on raw dictionaries and strings. This approach might be typical for some other languages (like Lisps), but not the way to go for Swift.
In Swift you should define your model classes, and parse your DataSnapshot as instances of those classes. For example if you start from:
struct Item {
let name: String
let title: String
}
class Category {
let name: String = ""
var items: [String: Item] = [:]
}
class TableDataModel {
var sections: [Category] = []
}
Then inside your observe, you can fill your TableDataModel, and then reload the table from the model. This way the Swift compiler helps you more to ensure that your program is correct, and the code is somewhat clearer.
In my Swift3 code I have an array:
var eventParams =
[ "fields" :
[ "photo_url":uploadedPhotoURL,
"video_url":uploadedVideoURL
]
]
Later on I want to add another array to this array, I thought I could just do:
eventParams["fields"]["user_location"] = [
"type":"Point", "coordinates":[appDelegate.longitude, appDelegate.latitude]
]
but I'm getting error here:
Type Any? has no subscript members
How can I add that array to my previously declared array fields?
Since your dictionary is declared as [String : Any], the compiler doesn't know that the value for "fields" is actually a dictionary itself. It just knows that it's Any. One very simple way to do what you're trying is like this:
(eventParams["fields"] as? [String : Any])?["user_location"] = [
"type":"Point", "coordinates":[appDelegate.longitude, appDelegate.latitude]
]
This will just do nothing if eventParams["fields"] is nil, or if it's not actually [String : Any].
You can also do this in a couple steps to allow for troubleshooting later on like this:
//Get a reference to the "fields" dictionary, or create a new one if there's nothig there
var fields = eventParams["fields"] as? [String : Any] ?? [String : Any]()
//Add the "user_location" value to the fields dictionary
fields["user_location"] = ["type":"Point", "coordinates":[appDelegate.longitude, appDelegate.latitude]]
//Reassign the new fields dictionary with user location added
eventParams["fields"] = fields
I am trying to save data to my plist file which contains definition of array of strings. My plist -enter image description here
My code for writing the data to my plist is --
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as! NSString
let path= documentsDirectory.stringByAppendingPathComponent("authors.plist")
let fileManager = NSFileManager.defaultManager( )
var plistArray = NSArray(contentsOfFile: path) as! [Dictionary<String , String>]
var addItem: Dictionary = ["link": linkBook.text , "Cover": cover.text , "title" : titlePage.text , "year" : year.text , "isbn" : isbn.text]
plistArray.append(addItem)
// I am getting the error here
(plistArray as NSArray).writeToFile(path, atomically: true)
My guess is that linkBook, cover, titlePage, year and isbn all represent text fields in your UI? The text property of one or more of whatever those things has a type of Optional String (String?). The plistArray variable is of type [String, String] – neither the keys nor the values can be nil.
Fundamentally you need to answer the question of what to do if the text property of one of the things mentioned above is nil. It could be the case that they are never empty in which case you could add exclamation points to them linkBook.text! and cover.text! etc.
If you do that, however, and any of them ever is nil, that will lead to a crash.
I suspect instead that what you want to do is use the ?? operator. Set up your dictionary as:
var addItem: Dictionary = ["link": linkBook.text ?? "",
"Cover": cover.text ?? "",
"title" : titlePage.text ?? "",
"year" : year.text ?? "",
"isbn" : isbn.text ?? ""]
That way if any of the ever is nil then you will get the empty string in your plist.
The textfield.text is an optional. So you should unwrap it first.
var addItem: Dictionary = ["link": linkBook.text! , "Cover": cover.text! , "title" : titlePage.text! , "year" : year.text! , "isbn" : isbn.text!]
Recently i update Xcode to 7 and it contains swift 2.0 compiler. Before this i made my project with older version of swift. In that version i had create NSMutableDictionary like bellow
let dictParams:NSMutableDictionary? = ["test" : "test",
"username" : txtEmail.text,
"password" : txtPassword.text,
"version" : "1.0",
"appId" : "1",
"deviceId" : "fasdfasdfrqwe2345sdgdfe56gsdfgsdfg"
];
in above code txtEmail.text and txtPassword.text is my text field and fill tha value at run time.
This code is properly working in older version of swift but after update to swift 2.0 it gives me an error like bellow
Cannot convert value of type '[String : String?]' to specified type 'NSMutableDictionary?'
what's wrong with it please guide me.
Simply by opening NSMutableDictionary or NSDictionary class interfaces from Xcode 7, you could easily see that the underlying type is actually [NSObject: AnyObject]. It means that the value can't be nil.
Unwrapping text values like txtEmail.text! or txtPassword.text! might look ok and help you to get rid of the compiling error, but it's a bad choice because technically text property of UITextField is optional and your app can crash in that case!
Do this for your safety:
let dictParams: NSMutableDictionary? = ["test" : "test",
"username" : txtEmail.text ?? "", // Always use optional values carefully!
"password" : txtPassword.text ?? "",
"version" : "1.0",
"appId" : "1",
"deviceId" : "fasdfasdfrqwe2345sdgdfe56gsdfgsdfg"
]
By the way, in case it's not critical to use NSMutableDictionary, please consider using Swift dictionary like this:
var mutableDictionary = [String: AnyObject]
// OR this if the value can be nil
var mutableDictionary = [String: AnyObject?]
simple change in
"username" : txtEmail.text! instead "username" : txtEmail.text
so final code is like bellow
let dictParams:NSMutableDictionary? = ["test" : "test",
"username" : txtEmail.text!, //Add ! here
"password" : txtPassword.text!, //Add ! here
"version" : "1.0",
"appId" : "1",
"deviceId" : "fasdfasdfrqwe2345sdgdfe56gsdfgsdfg"
];
txtEmail.text and/or txtPassword.text are returning an optional which is why its telling you to change the type. You could rewrite the dict as a swift dictionary (which is what I would recommend). When you do so, you can either make the object optional, or leave it as just String and bang out the optional string when you're creating the dict.
var optionalString: String?
var someDict: [String: String?] = ["test": "test", "test1": optionalString]