How can I have nil in the middle of nest Swift dictionaries?
var d: [String: AnyObject?] = [
"name": "Rodrigo",
"number": 1,
"nil": nil,
"more": [
"a": 1,
"b": 2,
"nil": nil
]
]
The second nil (inside "more") gives me the error Type of expression is ambiguous without more context.
The problem you're having here is that Swift's Dictionary is a value type, and therefore doesn't automatically conform to AnyObject. The compiler tries to help you out here, and bridge over to NSDictionary, which is an AnyObject, but unfortunately NSDictionary can't hold nil values, so you're out of luck.
As I see it, you have a couple options:
Go the standard Objective-C route and use NSNull() instead of nil in your nested array.
Rethink the structure of your data. Maybe instead of nested dictionaries, with string keys, you can build a struct PersonData that holds all the information you need in typed properties (including optionals). You may find that works even better than this approach.
Related
I have the following array in Swift:
var words = [
"English" : ["Hello", "Bye"],
"Spanish" : ["Hola", "Adios"]
]
How can I get the value for index, something as the following doesn't work
print(words["English"][0])
It throws the error: Value of optional type Array? not unwrapped, did you mean to use ! or ? but that just makes it:
print(words["English"]?[0])
and still doesn't work, please help.
You need to look into how to unwrap optionals. For example, what you are trying to do could be done either of these two ways:
Force unwrapping:
print(words["English"]![0])
Safe unwrapping:
if let hello = words["English"]?[0]{
print(hello)
}
I have below code in my project.
for (key, value) in photoDic {
if let url = URL.init(string: value as! String){
let photo : PhotoRecord = PhotoRecord.init(name:key as! String, url:url)
self.photoRecords.append(photo)
}
}
My question is how can I make key and value in for loop optional, or check if either of them are nil?
I am not able to check if they are nil, getting warning saying any cannot be nil because it is nonoptional.
I was thinking of using something like
for(key:String?, value:String?){}
But it is not working.
The key in a dictionary can't be an optional. (The key must conform to the Hashable protocol, and optionals don't.) So you CAN'T make the keys in your dictionary optional
If you want the values of your dictionary to be Optionals then you need to declare them as Optionals.
So, for example, change
let photoDic: [String: String] = ["key1": "http://www.someDomain.com/image.jpg"]
to
let photoDic: [String: String?] = ["key1": "http://www.someDomain.com/image.jpg"]
(Note that the type of photoDic is changed to [String: String?].)
As mentioned already all keys in a dictionary are non-optional by definition.
Further in NSDictionary all values are non-optional by definition, too.
Be happy about that because
There is no need to check for nil.
The code will never crash.
A Swift dictionary can theoretically contain optional values but practically you are discouraged from using it. For compatibility reasons to NSDictionary a nil value indicates key is missing.
I have this Array and I am trying to POST it to backend, but got really confused with all the casting
valuesDictionary=["medication": Optional("Novocain"), "dateOfBirth": Optional(2001- 01-01 00:00:00 +0000), "lastName": Optional("Berthold"), "allergies": Optional("Heuschnupfen"), "firstName": Optional("Alexander"), "Blutgruppe": Optional("A"), "PostalAddress": Optional(Eureka.PostalAddress(street: Optional("Gleimstraße"), state: nil, postalCode: Optional("10123"), city: Optional("Berlin"), country: Optional("DE")))]
trying to feed it into:
let request = Alamofire.request(.POST, Config.profileUpdate, parameters: valuesDictionary , encoding: .JSON)
I tried different things like:
let valuesDictionary = form.values() as! [String:AnyObject]
to downcast into the expected form but it just showing:
fatal error: can't unsafeBitCast between types of different sizes
Optionals aren't AnyObjects because Optional is an enum (a value type). You'll need to unwrap your optionals before you shove them in your dictionary.
I had some crazy stuff happening with the values I wanted to throw into Firebase. I ended up finding where they originally got away from their expectations and fixed that.
Wherever you've declared something that is different than what you want the final result to be, make it what the final result should be. In this case, wherever it's declared as anything other than an object, make it an object.
It's more work but it would save you some time in the end.
In this playground example I'm hoping to find an extension that will remove any nils I put into the creation of a dictionary.
var someValue: String?
if false {
someValue = "test"
}
var dict = ["key": "value",
"key2": someValue]
print("\(dict)")
dict["key3"] = nil
print("\(dict)")
In the above code the current log is
[AnyHashable("key2"): nil, AnyHashable("key"): Optional("value")]
[AnyHashable("key2"): nil, AnyHashable("key"): Optional("value")]
key3 is never added because setting = nil tells it to be removed. I would like to add that functionality to the initial creation of the dictionary but have yet to find a solution that works.
A working solution would result in the following print out
[AnyHashable("key"): Optional("value")]
[AnyHashable("key"): Optional("value")]
This is not solvable in Swift. The correct way to write it is:
var dict = ["key": "value"]
if false {
dict["key2"] = "test"
}
Swift doesn't provide the kind of syntax you're describing, and trying to force it to is going to break the type and create buggy situations. Do not try to create [AnyHashable: Any?]. That is a completely broken type that's going to burn you (Any? is completely broken as a type because Optional is Any, and anything can implicitly become Optional, so it becomes a bizarre recursive rabbit hole). [AnyHashable: Any] is acceptable if you must bridge to NSDictionary, but in general it should be strongly avoided and limited to just where you need it.
Note that this was much even more broken in ObjC (you could write this kind of stuff, but then it'd crash or truncate your dictionary, or some other weird bug), so at least we're making some progress.
In a lot of cases when I see people run into this problem, it's because they've overused optionals in the first place. In your user.name example, why is name optional in the first place? Is there any difference between nil and ""? If there isn't (and there usually isn't), then just make name non-optional (nonnullable in ObjC) and default it to empty and lots of problems go away. Having two versions of the same value (i.e. nil and "" have the same meaning) indicates a type problem, not a syntax problem.
If you want to simplify the syntax just a little bit with a quick extension that works on key/value? pairs like this:
extension Dictionary {
init(keyOptionalPairs: [(Key, Value?)]) {
var d: [Key: Value] = [:]
for (key, value) in keyOptionalPairs {
d[key] = value
}
self = d
}
}
let keyValues: [(String, String?)] = [
("key", "value"),
("key2", nil)
]
let dict = Dictionary(keyOptionalPairs: keyValues)
But notice that the Dictionary is [String: String], not [String: String?]. That's on purpose.
Compared to your syntax, it just adds a set of parens. But compare to the non-fancy version, which isn't beautiful, but is very straightforward.
let dict: [String: String] = {
var d: [String: String] = [:]
d["key"] = "value"
d["key2"] = nil
return d
}()
In Swift The Programming Language Book, I quote:
You can use subscript syntax to remove a key-value pair from a dictionary by assigning a value of nil for that key
They said that assigning a value to nil while remove the pair, but it is not mentioned while initializing the dictionary, so I think it is not valid.
If you really need to do that, I suggest to do some logic after initializing the dictionary to do that for you, like that:
for (key, value) in dict {
if value == nil {
dict[key] = nil
}
}
Swift only allows a dictionary to contain a single type.
Here's the definition that is taken from the Swift book:
A dictionary is a container that stores multiple values of the same type
[...]
They differ from Objective-C’s NSDictionary and NSMutableDictionary classes, which can use any kind of object as their keys and values and do not provide any information about the nature of these objects.
If that’s the case then how are we going to create nested dictionaries?
Imagine we have a plist that holds String, Array and Dictionary items in it . If I’m allowed to hold only the same of type of items (either string, array etc.) then how am I going to use different types of items stored in the plist?
How do I put different types in the same dictionary in Swift?
You can achieve plist-like nested structures using Any type for dictionary values which is Swift's somewhat counterpart to Objective-C's id type but can also hold value types.
var response = Dictionary<String, Any>()
response["user"] = ["Login": "Power Ranger", "Password": "Mighty Morfin'"]
response["status"] = 200
EDIT:
Any seems to be better than AnyObject because in the above code response["status"] is of type Swift.Int, while using value type of AnyObject it is __NSCFNumber.
As has been suggested, you can use the Any type to represent a plist dictionary's values. But then how do you work with the data? Cast every value any time you look it up from the dictionary? That's really messy. A better, more type-safe way to model a plist would be to take advantage of Swift's enums, also known as algebraic data types or discriminated unions. They let you specify exactly what types are permitted in the dictionary and avoid ever having to cast. Here's an implementation, explained:
// An atomic (i.e. non-collection) data type in a plist.
enum PListNode {
case PLN_String(String)
case PLN_Integer(Int)
case PLN_Float(Double)
case PLN_Bool(Bool)
case PLN_Date(CFDate)
case PLN_Data(CFData)
}
At the most atomic level, only the above data types may be stored in a plist. Each 'node' in the plist can ultimately can only be one of these types. So we create an enum which lets us specify this.
// A value that can be stored in a plist Dictionary's key-value pair.
enum PListValue {
case PLV_Node(PListNode)
case PLV_Array(PListNode[])
case PLV_Dictionary(Dictionary<String, Box<PListValue>>)
}
typealias PList = Dictionary<String, Box<PListValue>>
A plist is basically a dictionary of key-value pairs, and each value can be either an atomic (i.e. non-collection) value; or it can be an array of atomic values; or it can be a dictionary of string-plist value pairs. The above enum expresses these constraints, and the typealias gives the plist type an easy-to-remember name.
Given the above types, we can completely express any given plist in a type-safe way, e.g.:
// Example translated from
// https://developer.apple.com/library/Mac/documentation/Darwin/Reference/ManPages/man5/plist.5.html
let myPlist: PList = [
"Year Of Birth": Box(PLV_Node(PLN_Integer(1965)))
, "Pets Names": Box(PLV_Array([]))
, "Picture": Box(PLV_Node(PLN_Data(...)))
, "City of Birth": Box(PLV_Node(PLN_String("Springfield")))
, "Name": Box(PLV_Node(PLN_String("John Doe")))
, "Kids Names": Box(
PLV_Array([PLN_String("John"), PLN_String("Kyra")])
)
]
What it means to be type-safe here is that you can process any given plist using a switch statement and cover all possibilities without the need for any casting. You're eliminating a whole class of potential runtime errors. E.g.:
// See https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-XID_189 for explanation
switch myPlist["Year Of Birth"] {
case Box(.PLV_Node(let plvNodeValue)):
...
case Box(.PLV_Array(let plvArrayValue)):
...
case Box(.PLV_Dictionary(let plvDictionaryValue)):
...
}
Note that it's necessary to wrap up recursive data structures in a 'box' (a pointer to the actual value) to keep their sizes finite.
NSObject works for my case while "Any" does not
var d:Dictionary<String,NSObject> = [:]
d["key1"] = "ddd"
d["key2"] = 111 //OK
NSLog("%#", d) //OK
var d2:Dictionary = Dictionary<String,Any>()
d2["key1"] = "ddd"
d2["key2"] = 111
NSLog("%#", d2) //I got error here
Use NSMutableDictionary like this :
var dictInfo : NSMutableDictionary = [ "lang_key": "1"]
dictInfo["food_type"] = lbl_TypeOfFood.text
dictInfo["search_text"] = txt_Search.text
dictInfo["date"] = lbl_Date.text
dictInfo["opening_hours"] = lbl_OpeningHours.text
hope this will work fine .
Use: Dictionary<String, AnyObject>
var dict: Dictionary<String, AnyObject> = [
"number": 1,
"string": "Hello",
]
NSMutableDictionary to Dictionary works like a charm and will allow you to put different types in a Dictionary in the Swift Language:
let nsMutableDictionary = NSMutableDictionary()
nsMutableDictionary[NSFontAttributeName] = UIFont(name: "HelveticaNeue", size: 12.0)!
nsMutableDictionary[NSForegroundColorAttributeName] = UIColor.redColor()
let dictionary: Dictionary<NSObject, AnyObject> = nsMutableDictionary
self.attributedPlaceholder = NSAttributedString(string: textParam, attributes: dictionary)
let dictionary : Dictionary = [
"key": "value",
"key2": 2,
"key3": NSString(),
2: "test",
]
One can specify types which restricts the dictionary
let dictionary : Dictionary<String, String> = [
"key": "value",
"key2": 2, // This errors
]