Trouble Unpacking Dictionary of Dictionaries in Swift - ios

I am having a devil of a time trying to work with Dictionaries in Swift. I have created the following Dictionary of Dictionaries but I am unable to unpack it.
var holeDictionary = Dictionary<String,Dictionary<String,Dictionary<String,Int>>>()
I can get the first Dictionary out with:
var aDictionary = holeDictionary["1"]
But trying to access the next Dictionary within it gives me an error as follows:
var bDictionary = aDictionary["key"] // [String : Dictionary<String, Int>]?' does not have a member named 'subscript'
I know what the contents of the Dictionaries are and can verify them with a println(aDictionary). So how can I get to the Dictionaries buried deeper down?

The key subscript on Dictionary returns an optional, because the key-value pair may or may not exist in the dictionary.
You need to use an if-let binding or force unwrap the optional before you can access it to subscript it further:
if let aDictionary = holeDictionary["1"] {
let bDictionary = aDictionary["key"]
}
Edit, to add forced unwrap example:
If you're sure that the key "1" exists, and you're okay with assert()ing at runtime if the key doesn't exist, you can force-unwrap the optional like this:
let bDictionary = holeDictionary["1"]!["key"]
And if you're sure that the key "key" will exist, you'd do this instead:
let bDictionary = holeDictionary["1"]!["key"]!

Accordingly to the swift documentation:
Because it is possible to request a key for which no value exists,
a dictionary’s subscript returns an optional value of the dictionary’s
value type
When you retrieve an item from a dictionary you have an optional value returned. The correct way to handle your case is:
var holeDictionary = Dictionary<String,Dictionary<String,Dictionary<String,Int>>>()
if let aDictionary = holeDictionary["1"] {
var bDictionary = aDictionary["key"]
}

Related

Why print() is printing my String as an optional?

I have a dictionary and I want to use some of its values as a key for another dictionary:
let key: String = String(dictionary["anotherKey"])
here, dictionary["anotherKey"] is 42 but when I print key in the debugger I see the following:
(lldb) expression print(key)
Optional(42)
How is that possible? To my understanding, the String() constructor does not return an optional (and the compiler does not complain when I write let key: String instead of let key: String?). Can someone explain what's going on here?
As a sidenote, I am currently solving this using the description() method.
This is by design - it is how Swift's Dictionary is implemented:
Swift’s Dictionary type implements its key-value subscripting as a subscript that takes and returns an optional type. [...] The Dictionary type uses an optional subscript type to model the fact that not every key will have a value, and to give a way to delete a value for a key by assigning a nil value for that key. (link to documentation)
You can unwrap the result in an if let construct to get rid of optional, like this:
if let val = dictionary["anotherKey"] {
... // Here, val is not optional
}
If you are certain that the value is there, for example, because you put it into the dictionary a few steps before, you could force unwrapping with the ! operator as well:
let key: String = String(dictionary["anotherKey"]!)
You are misunderstanding the result. The String initializer does not return an optional. It returns the string representation of an optional. It is an non-optional String with value "Optional(42)".
A Swift dictionary always return an Optional.
dictionary["anotherKey"] gives Optional(42), so String(dictionary["anotherKey"]) gives "Optional(42)" exactly as expected (because the Optional type conforms to StringLiteralConvertible, so you get a String representation of the Optional).
You have to unwrap, with if let for example.
if let key = dictionary["anotherKey"] {
// use `key` here
}
This is when the compiler already knows the type of the dictionary value.
If not, for example if the type is AnyObject, you can use as? String:
if let key = dictionary["anotherKey"] as? String {
// use `key` here
}
or as an Int if the AnyObject is actually an Int:
if let key = dictionary["anotherKey"] as? Int {
// use `key` here
}
or use Int() to convert the string number into an integer:
if let stringKey = dictionary["anotherKey"], intKey = Int(stringKey) {
// use `intKey` here
}
You can also avoid force unwrapping by using default for the case that there is no such key in dictionary
var dictionary = ["anotherkey" : 42]
let key: String =
String(dictionary["anotherkey", default: 0])
print(key)

How to cast Dictionary in Swift to related type?

This is what I am trying to do with the dictionary:
if let deliveries = dictionary["deliveries"] as? NSDictionary {
var castedDeliveries = [Double: Double]()
for delivery in deliveries {
if let value = delivery.value as? Double {
castedDeliveries[Double(delivery.key as! NSNumber)] = value //Could not cast value of type 'NSTaggedPointerString' (0x1a1e3af20) to 'NSNumber' (0x1a1e458b0).
}
}
settings!.deliveries = castedDeliveries
}
And this is what I try to cast, as a part of JSON response from server:
deliveries = {
2 = 0;
5 = "2.59";
7 = "3.59";
};
It doesnt work, because there is an error at commented line:
Could not cast value of type 'NSTaggedPointerString' (0x1a1e3af20) to 'NSNumber' (0x1a1e458b0).
You are trying to cast dictionary directly but instead you need to cast each key - value pair. If you want generic solution to this problem take a look at SwiftyJSON library which address JSON parsing problem for you.
Casting doens't mean data transformation from a type to another.
Your dictionary seems to be composed by Integer keys and String values.
If you want to transform in something else you ca use the map function.
let converted = deliveries.map{[Double($0) : Double($1)]}
But pay attention.
Here we are saying, iterate over the dictionary (in the $0 there is the dictionary key in the $1 there is the value) and create a new dictionary that has as a key a Double initialized at the key value and as a new value a Double initialized as the old dictionary value. The last conversion can fail, so the returned data is an optional.
As I noted in the comments, this isn't casting. You want a data conversion. You need to do that explicitly, especially in this case since it might fail.
Looking at the error, I think you really have a dictionary of [String:String] here (in NSDictionary form). That suggests the JSON is badly encoded, but such is life. Assuming that dictionary looks something like this:
let dictionary: NSDictionary = ["deliveries": ["2":"0", "5": "2.59", "7": "3.59"]]
You would convert it to [Double:Double] like this:
if let jsonDeliveries = dictionary["deliveries"] as? [String:String] {
var deliveries: [Double: Double] = [:]
for (key, value) in jsonDeliveries {
if let keyDouble = Double(key),
valueDouble = Double(value) {
deliveries[keyDouble] = valueDouble
}
}
// Use deliveries
}
This silently ignores any values that can't be converted to Double. If you would rather generate errors, use a guard let rather than an if let.

using key : value after loading dictionary from a plist using swift

I'm using Xcode 6 with Swift.
When I create a dictionary programmatically, I can print out by key or value or create arrays. When I create and load a dictionary from a plist, I can print the plist in total, but can't create arrays or print either the keys or values. The error message is 'NSDictionary?' does not have a member named 'values'.
I notice the programmatic dictionary is printed inside brackets with a colon between key and value, but the plist loaded dictionary is printed inside braces with = signs between the key and value. I assume this may be the problem, but don't know why it formatted this way or how to fix it.
// Programmatic Dictionary Code
var newDict:[String:String] = ["car":"ground", "airplane":"sky", "boat":"water"]
println("newDict key:value are: \(newDict)")
//Prints Out as expected
newDict key:value are: [car: ground, boat: water, airplane: sky]
// plist read to Dictionary Code
var type : NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("transport", ofType: "plist") {
type = NSDictionary(contentsOfFile:path)
}
println(type)
// print out isn't the same as above
Optional({
airplane = sky;
boat = water;
car = ground;
})
Trying to access the key or the element in this loaded dictionary returns an error message indicating the dictionary doesn't have a member ....
I've been unable to find anything to explain why the loaded dictionary would be different or how to access the members.
You need to unwrap your optionals. An optional is a variable that can hold a value just like any other variable, but it can also hold no value at all, or nil.
This code:
var type : NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("transport", ofType: "plist") {
type = NSDictionary(contentsOfFile:path)
}
println(type)
should be:
var type : NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("transport", ofType: "plist") {
type = NSDictionary(contentsOfFile:path)
}
println(type!)
Anytime you use type (or any other optional), you need to unwrap it with !, ?, or if let.
Note that if type contained nil and you unwrap it with !, your app will crash, saying "unexpectedly found nil while unwrapping optional value".
For more information about optionals, see The Swift Programming Language: The Basics.

I can't downcast an NSDictionary, CFDictionaryRef, nor an NSMutableDictionary, to SecItemAdd()

I don't understand Keychain that well. As far as I know you want to send a CFDictionaryRef to SecItemAdd() to store the attributes in applications' keychain. I'm probably not understanding unwrapping or something. I feel like the answer is obvious but this is my first experience with something so low-level, so please be patient with me. :)
Here's my Swift:
var keychainDictionary : NSMutableDictionary? = NSMutableDictionary()
var username = "Arcrammer"
var password = "somePassword"
var site = "http://www.hausofalexander.tk/"
// Item class
keychainDictionary![(kSecClass as String)] = kSecClassInternetPassword
// Attributes
keychainDictionary![(kSecAttrAccount as String)] = username
keychainDictionary![(kSecAttrService as String)] = "Haus"
keychainDictionary![(kSecAttrAccessible as String)] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly as CFTypeRef)
keychainDictionary![(kSecMatchCaseInsensitive as String)] = (kCFBooleanTrue asCFTypeRef)
keychainDictionary![(kSecReturnData as String)] = (kCFBooleanTrue as CFTypeRef)
// Completely mutable NSDictionary from the NSMutableDictionary
var result : OSStatus?
SecItemAdd(keychainDictionary!, result)
I've created an empty and optional NSMutableDictionary() to hold the Keychain dictionary attributes and values. Next, I add add some kSecAttr- values to the dictionary as keys. I'm also casting them as String because the compiler throws the error LoginViewController.swift:112:37: Type 'CFStringRef' does not conform to protocol 'NSCopying' if I try to them as CFTypeRef or I don't try to cast them at all. Of course, String does conform to the NSCopying protocol. I don't know if this has an effect on the attribute value, but it definitely dismisses the error.
After that I continue to pass information as the String data type. From what I've gathered (which isn't much, tbh) CFTypeRef is an enumeration and the data returned by something like kCFBooleanTrue will return the type value from that enumeration which would be something like CFBoolean, CFDictionary, or basically what AnyObject or id would be to Foundation.
After that I proceed to create an empty variable named result with the type OSStatus. This variable will hold the result of the AddSecItem() function following on line 118.
However, I'm given the following error:
swift:118:17: Cannot convert the expression's type '(#lvalue NSMutableDictionary, #lvalue OSStatus?)' to type 'CFDictionary!'
I have no idea what this means and I can't figure it out. What in the world am I doing wrong here?
I don't know the parameter supply for SecItemAdd but you need more something like
var idontknow = UnsafeMutablePointer<Unmanaged<AnyObject>?>()
result = SecItemAdd(keychainDictionary!, idontknow)
Try to find out from the docu what idontknow exactly is.

How to create and access a NSDictionary in Swift

I am busy converting to Swift and am trying to figure out how to do the following in Swift
NSArray arrayOfStrings1 = {#"Substring1", #"Substring2", nil};
Dictionary dict = {#"MainString1", arrayOfStrings1};
So in Swift I have the following:
var dictionary = [String: Array<String>]() // is this correct ??
var array: [String] = ["Substring1", "Substring2"]
dictionary["MainString1"] = ["Substring1.1", "Substring1.2"]
dictionary["MainString2"] = ["Substring2.1", "Substring2.2"]
Now in order to access the array I use
let array = dictionary["MainString1"]
let item0 = array[0]
but this fails with a compiler error which seems to indicate that array is in fact a String not an array of strings.
What am I missing here?
The issue is actually that a subscript lookup for a Dictionary in Swift returns an optional value:
This is a pretty great feature - you can't be guaranteed that the key you're looking for necessarily corresponds to a value. So Swift makes sure you know that you might not get a value from your lookup.
This differs a little bit from subscript behavior for an Array, which will always return a value. This is a semantically-driven decision - it's common in languages for dictionary lookups to return null if there is no key - but if you try to access an array index that does not exist (because it's out of bounds), an exception will be thrown. This is how Swift guarantees you'll get a value back from an array subscript: Either you'll get one, or you'll have to catch an exception. Dictionaries are a little more lenient - they're "used to" not having the value you're asking for.
As a result, you can use optional binding to only use the item if it actually has a value, like so:
if let theArray = dictionary["MainString1"] {
let item0 = theArray[0]
} else {
NSLog("There was no value for key 'MainString1'")
}

Resources