How would you use .updateValue() to add a sub-dictionary to a UserDefaults dictionary? - swift

Is this proper syntax for this line of code? If not what would be the correct syntax and why so?
UserDefaults.standard.dictionary(forKey: "mainDict")?.updateValue(subDict, forKey: "subDictTitle")

First, you have to store Userdefault dictionary to a temporary dictionary. Then you have to add data to a temporary dictionary.
No need to update the dictionary to Userdefault. When you store Dictionary to the Usedefault with the same key, it will replace the older dictionary to the new one.
UserDefaults.standard.set(YOUR_TEMPORARY_DICTIONARY, forKey: YOUR_KEY_NAME)

The updateValue(_:forKey:) is a mutating instance method for the dictionary, which means that it updates the value of the dictionary. Obviously, In order to mutate an instance, it has to be mutable, which is not the case when calling UserDefaults.standard.dictionary(forKey: "mainDict").
Even if you did:
let myDict = ["k1": "Hello"]
UserDefaults.standard.register(defaults: ["myDict": myDict])
var mutable = UserDefaults.standard.dictionary(forKey: "myDict")!
mutable["k1"] = "HEY"
print(UserDefaults.standard.dictionary(forKey: "myDict")) // Optional(["k1": Hello])
the value of the dictionary set in the user default won't change because simply mutable is a copy of it.
To clarify, it's similar to implementing:
UserDefaults.standard.register(defaults: ["k2": "this is my string"])
UserDefaults.standard.string(forKey: "k2") = "new string"
which generates the error of
Expression is not assignable: function call returns immutable value
So, in order to resolve this issue, what you should do is to set a new value (updated dictionary) to the user defaults with the same key:
var myDict = UserDefaults.standard.dictionary(forKey: "myDict")
myDict?.updateValue("Hey", forKey: "k1")
UserDefaults.standard.set(myDict, forKey: "myDict")

Related

How setting 'nil' as a value of [String: String] dictionary is valid?

I'm curious why this snippet works correctly in Playground:
var dict = [String: String]()
dict["key"] = nil
let value = dict["key"]
we can see that I declared Dictionary with non optional values, after checking it in Playground it works similarly to dictionary declared as [String: String?]
My question is where is the difference in terms of setting nil value between dictionary declared as [String: String] and [String: String?] ?
dict["key"] = nil is a shorthand to removing the key from the dictionary (same as using dict.removeValue(forKey: "key")). If there was a value under the "key" key, after this line the whole entry is removed from the dictionary (both the key and the value).
Read the subscripts docs to learn more:
If you assign nil as the value for the given key, the dictionary removes that key and its associated value.
In the following example, the key-value pair for the key "Aquamarine" is removed from the dictionary by assigning nil to the key-based subscript.
hues["Aquamarine"] = nil
print(hues)
// Prints "["Coral": 18, "Heliotrope": 296, "Cerise": 330]"
let value = dict["key"] gets the value for the key, and by definition returns nil if there is no entry for the given key (which is in your case).
According to docs, subscript returns either the value, or nil, if the key is not in the dictionary:
The value associated with key if key is in the dictionary; otherwise, nil.
dict["key"] = nil means you are removing the item from dictionary. The return type of dict[someKey] is Optional as the key may not be there.
You could consider implementing:
dict["key"] = nil
as the same as calling removeValue(forKey:):
dict.removeValue(forKey: "key")
By default, getting a value from a dictionary would returns an optional value:
var dict = [String: String]()
dict["key"] = "Hello"
let value = dict["key"] // Optional("Hello")
Note that the type of value is optional string (String?).

Iterating dictionary swift 3

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.

Dictionary saved to NSUserDefaults always returns nil

I'm trying to save a dictionary to NSUserDefaults using the setObject() function but when I use the objectForKey() function to retrieve the dictionary it returns nil. Why is this happening?
var data = NSUserDefaults.standardUserDefaults();
var scoreboard = [Int : String]()
let scores = "scoresKey"
scoreboard[3] = "spencer"
scoreboard[6] = "brooke"
scoreboard[11] = "jason"
data.setObject(scoreboard, forKey: scores)
data.objectForKey(scores) // Returns nil
The first problem was that it's not possible to use NSUserDefaults in a Playground.
See: https://stackoverflow.com/a/31210205/3498950
A second problem is found when the code above runs in a normal iOS project. It throws an NSInvalidArgumentException since the dictionary was a non-property list object because the keys needed to be of type String.
Although NSDictionary and CFDictionary objects allow their keys to be
objects of any type, if the keys are not string objects, the
collections are not property-list objects.
See: "What is a Property List?" in the Apple Docs

Why aren't [String:AnyObject?] and [String:AnyObject] the same type to the swift compiler?

Semantically speaking, [String:AnyObject?] and [String:AnyObject] are the same thing in terms of the way they act, meaning they will return the same thing if I access a key that wasn't set and setting a key to nil will remove that key from the dictionary. Why aren't they considered the same type?
EDIT: I understand the difference from the compiler point of view from the answers. I decided to put the following code in the playground:
var optional = [String:AnyObject?]()
var regular = [String:AnyObject]()
//Some control keys
optional["controlkey"] = "valueoptional"
regular["controlkey"] = "valueRegular"
//Set the keys
optional["keyOptional"] = "valueoptional"
regular["keyRegular"] = "valueRegular"
//Unset the keys
optional["keyOptional"] = nil
regular["keyRegular"] = nil
for (key,val) in optional {
print("key: \(key)\tval: \(val)")
}
for (key,val) in regular {
print("key: \(key)\tval: \(val)")
}
To my surprise the optional did not print the key that was set to nil.
Output was as follow:
key: controlkey val: Optional(valueoptional) //The keys for the optional dictionary
key: controlkey val: valueRegular //The keys for the `regular` dictionary
Why doesn't the key that I set to nil show up?
From the Apple docs:
The Swift language defines the postfix ? as syntactic sugar for the
named type Optional, which is defined in the Swift standard
library.
The type AnyObject? is an enumeration with two cases, None and Some(Wrapped), which are used to represent values that may or may not be present. But AnyObject is responding to one that will be presented.
Setting nil for a key in dictionary means removing the element itself.
Try to set NSNull()
optional["keyOptional"] = NSNull()
Because a nullable type and a non nullable type aren't the same thing from the compiler point of view. It just happens that the Dictionnary interface eventually "blend them" in similar entity, but this is linked with the Dictionnary implementation, not really with the type system.
EDIT: Your update changes the context of the question, but setting to nil is a way to unset from my understanding.
They are different because you can actually store nil into [String:AnyObject?]. You cannot do it using optional[key] = value (subscript operator) because that one has a special behavior for nil (removes value) but you can do it:
var optional: [String:AnyObject?] = ["test": nil]
optional.updateValue(nil, forKey: "test2")
print(optional) // ["test2": nil, "test": nil]
Of course, getting a value from such a dictionary:
print(optional["test"]) // Optional(nil)
results in a double optional Optional<Optional<AnyObject>> (or AnyObject??) and interaction with such types is cumbersome so you should avoid storing nil into dictionaries.
Because setting a key to nil remove that key from the dictionary! From the documentation
Reading a key that is not present in self yields nil. Writing nil as the value for a given key erases that key from self
You can't store nil as a dictionary value. If you need something denote nothingness, use NSNull:
optional["keyOptional"] = NSNull()
regular["keyRegular"] = NSNull()
They aren't the same type because they aren't the same type ;-) Compilers are such literal creatures.
#Sulthan's answer is correct and more complete - I didn't see it until after I hit post on my own answer. But if you look up a key in a [String: AnyObject?] dictionary and a value is present, you'll get an optional wrapped in an optional - the result is an AnyObject??. You'd have to unwrap it twice to use it:
var optional = [String: AnyObject?]()
// I changed it to NSString because String isn't an AnyObject
v
optional["controlkey"] = NSString(string: "valueoptional")
print(optional["controlkey"]) // prints "Optional(Optional(valueoptional))"
if let val = optional["controlkey"] {
print(val) // prints "Optional(valueoptional)"
if let unwrappedVal = val {
print(unwrappedVal) // prints "valueoptional"
}
}

How to store and retrieve Dictionary value types in Swift

I was having trouble storing a Dictionary in the NSUserDefaults, and then I had trouble retrieving the values. After a failed search on the web, I finally figured it out and so I hope this helps you with this same issue.
Define your variables for NSUserDefaults, Dictionary and key:
let defaults = NSUserDefaults.standardUserDefaults()
var myDictionary = Dictionary<String, Int>()
let myKey = "Key"
How to save your dictionary:
defaults.setObject(myDictionary, forKey: myKey)
How to retrieve your dictionary:
myDictionary = defaults.dictionaryForKey(myKey) as! Dictionary<String, Int>
NOTE: make sure that your data types are the same as the way you set them up.
*** FYI, this is using Xcode 6.3.2