Unable to access userDefaults with standard - swift

I have trouble with UserDefaults.
I already tried userDefaults.standard but it's not working. The userDefeaults with standard shows an error:
"which is "Static member 'standard' cannot be used on instance of type 'UserDefaults'"
if user != nil {
if userDefaults.standard.object
}
Static member 'standard' cannot be used on instance of type 'UserDefaults'
Why can not accept the standard with userDefaults?

Here is how you can work with UserDefaults.
Get a reference to user defaults:
let defaults = UserDefaults.standard
Set an object for key "age":
defaults.set(31, forKey: "age")
Unwrap the value of the stored key. If it is nil (key does not exist), it will not enter the block.
if let object = defaults.object(forKey: "age") {
}

Related

Swift Userdefaults converting String to __NSCFString

I have code that save a dictionary of [String: Any] in UserDefaults. On retrieval String are changed to __NSCFString. I am using Mixpanel to track events and sends this dictionary as events properties. Now the problem is __NSCFString is not a valid MixpanelType so Mixpanel is discarding my dictionary.
Questions:
Is there a way to get same datatypes that are saved using dictionary in UserDefaults?
Is there a way Mixpanel accepts converted datatypes?
Here is a code I am using
var mixpanelProperties: [String: Any] {
get { defaults.dictionary(forKey: "\(#function)") ?? [:] }
set { defaults.set(newValue, forKey: "\(#function)") }
}
mixpanelProperties = ["a-key": "value for the key"]
let prop = mixpanelProperties
print("Type of: \(String(describing: prop["a-key"]))")
asdad
MacOS and iOS use a variety of different classes to represent strings, which are all compatible. x as? String should just work, no matter what the concrete class of x is. Unless you use code that explicitely checks the class which you shouldn't do.

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

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")

Top-level Bool encoded as number property list fragment. PropertyListEncoder

I have this generic function to save in NSUserDefaults, in generally works but now I want to save a boolean value and I get an error. I could not find anything and I do not understand why it is not working.
extension UserDefaults {
func saveUserDefaults<T: Codable>(withKey key: String, myType: T) throws{
do {
let data = try PropertyListEncoder().encode(myType)
UserDefaults.standard.set(data, forKey: key)
print("Saved for Key:", key)
} catch let error {
print("Save Failed")
throw error
}
}
I am calling it like this:
try! UserDefaults().saveUserDefaults(withKey: "String", myType: false)
This is the error I get. I know there is an other way to save boolean values, but I am wondering why it is not working like this?
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.EncodingError.invalidValue(false,
Swift.EncodingError.Context(codingPath: [], debugDescription:
"Top-level Bool encoded as number property list fragment.",
underlyingError: nil))
Thanks!
A PropertyListEncoder encodes to a “property list,” and that is always an
array or a dictionary, compare PropertyListSerialization.
Therefore
let data = try PropertyListEncoder().encode(myType)
fails if myType is a Bool (or anything which is not an array
or a dictionary).
The possible objects in a property list are also restricted, they can only be instances of
NSData, NSString, NSArray, NSDictionary, NSDate, or NSNumber – or of Swift types
which are bridged to one of those Foundation types.
As #Martin said a PropertyListEncoder supports only property lists on top level, but not a single fragment of property list like NSNumber.
A very simple (though not very elegant) workaround is to wrap any object into array:
let data = try PropertyListEncoder().encode([myType])
UserDefaults.standard.set(data, forKey: key)
And decode it as:
let arr = try PropertyListDecoder().decode([T].self, from: data)
return arr.first
see https://www.marisibrothers.com/2018/07/workaround-for-serializing-codable-fragments.html
You do not need to encode a Boolean value to save into UserDefaults. You can directly save the boolean into the UserDefaults by calling
let myValue: Bool = false
UserDefaults.standard.set(myValue, forKey: "key")
See Apple docs: UserDefaults.set(_:forKey:).
In fact, Float, Double, Integer, Bool and URL types do not need to be encoded and can directly be saved to UserDefaults.
I see that you have a function that takes a Codable type, encodes it and then saves it to UserDefaults. As Martin R pointed out, you will have to modify that function and check whether passed object can be directly saved to UserDefaults without a need for encoding. It is not necessarily pretty but something like this could work:
switch objectToSave {
case let aFloatType as Float:
UserDefaults.standard.set(aFloatType, forKey: "key")
case let aDoubleType as Double:
UserDefaults.standard.set(aDoubleType, forKey: "key")
case let anIntegerType as Int:
UserDefaults.standard.set(anIntegerType, forKey: "key")
case let aBoolType as Bool:
UserDefaults.standard.set(aBoolType, forKey: "key")
case let aURLType as URL:
UserDefaults.standard.set(aURLType, forKey: "key")
default:
//encode the object as a PropertyList and then save it to UserDefaults
do {
let data = try PropertyListEncoder().encode(objectToSave)
UserDefaults.standard.set(data, forKey: key)
} catch let error {
//the object you passed cannot be encoded as a PropertyList
//possibly because the object cannot be represented as
//NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary - or equivalent Swift types
//also contents of NSArray and NSDictionary have to be one of the types above
print("Save Failed: \(error)")
//perform additional error handling
}
}

Enabling App SandBox lead to a Crash when getting UserDefault Dictionary Value

I have tested with the debugger and both the Dictionary and the value I am interested in are not nil. When I enable the App Sandbox the app crashes, but when I disable it everything works ok. Is there something to do when using UserDefaults with Sandbox enabled?
Code where the crash occurs:
func getPreferenceSettings(_ aKey: String) -> Bool
{
var lastPreferenceSetting: Bool!
if let currentSetting: Dictionary<String, Bool> = UserDefaults.standard.object(forKey: "defaultValues") as? Dictionary<String,Bool>{
lastPreferenceSetting = currentSetting[aKey]! //crash at this line
}else{
//show error to the user
}
return lastPreferenceSetting
}
I am using Xcode 9.4
You have to check if both keys defaultValues and aKey exist. As the return value is non-optional return false as default value.
This code uses the dedicated method dictionary(forKey
func getPreferenceSettings(_ aKey: String) -> Bool
{
if let currentSetting = UserDefaults.standard.dictionary(forKey: "defaultValues"),
let lastPreferenceSetting = currentSetting[aKey] as? Bool {
return lastPreferenceSetting
} else {
//show error to the user
}
return false
}
The key value was nil (i was using the Release schemes instead of the Debug, that is why i was not able to see its value and assumed it was not nil). Also with the help of the debugger i was able to see that the key was never saved in the dictionary. The strange thing is that when disabling the Sandbox the crash doesn't occur. I have modified the method in the following way so it returns an optional and then the caller checks with optional binding if there is a key or not.
func getPreferenceSettings(_ aKey: String) -> Bool?
{
var lastPreferenceSetting: Bool?
if let currentSetting: Dictionary<String, Bool> = UserDefaults.standard.object(forKey: "defaultValues") as? Dictionary<String,Bool>{
lastPreferenceSetting = currentSetting[aKey]
}else{
//show error to the user
}
return lastPreferenceSetting
}
EDIT
#vadian
I was reading when to use correctly the ? type and it's commonly used to indicate the absence of a value for example the mid name of a Person. But in this case the preference property is always present in the project because it's hard coded by the developer (except, like happened to me when i forgot to add it to the UserDefaults and had first to clear it before update the dictionary with the new key ). If i understand correctly, from what you have write, i should return an Bool? value and then check with optional binding in the caller method if it's nil or not; this should be done not only for the mid name case for the user side but also for the developer side because there could be another developer that could pass a wrong aKey, right?
Also is it correct to declare the lastPreferenceSetting as ?, because if i use a default value and the if let evaluate to false, the function could return not the expected value.

How to add nil value to Swift Dictionary?

I have made a request to my server in my app. And posted data something like this.Server side is waiting for all parameters even they are nil. But i couldn't add nil values to dictionary.
var postDict = Dictionary<String,AnyObject>
postDict[pass]=123
postDict[name]="ali"
postDict[surname]=nil // dictionary still has only pass and name variables.
Is there a way to add nil value to dictionary ?
How to add nil value to Swift Dictionary?
Basically the same way you add any other value to a dictionary. You first need a dictionary which has a value type that can hold your value. The type AnyObject cannot have a value nil. So a dictionary of type [String : AnyObject] cannot have a value nil.
If you had a dictionary with a value type that was an optional type, like [String : AnyObject?], then it can hold nil values. For example,
let x : [String : AnyObject?] = ["foo" : nil]
If you want to use the subscript syntax to assign an element, it is a little tricky. Note that a subscript of type [K:V] has type V?. The optional is for, when you get it out, indicating whether there is an entry for that key or not, and if so, the value; and when you put it in, it allows you to either set a value or remove the entry (by assigning nil).
That means for our dictionary of type [String : AnyObject?], the subscript has type AnyObject??. Again, when you put a value into the subscript, the "outer" optional allows you to set a value or remove the entry. If we simply wrote
x["foo"] = nil
the compiler infers that to be nil of type AnyObject??, the outer optional, which would mean remove the entry for key "foo".
In order to set the value for key "foo" to the AnyObject? value nil, we need to pass in a non-nil outer optional, containing an inner optional (of type AnyObject?) of value nil. In order to do this, we can do
let v : AnyObject? = nil
x["foo"] = v
or
x["foo"] = nil as AnyObject?
Anything that indicates that we have a nil of AnyObject?, and not AnyObject??.
You can use the updateValue method:
postDict.updateValue(nil, forKey: surname)
As documented in here, setting nil for a key in dictionary means removing the element itself.
If you want null when converting to JSON for example, you can use NSNull()
var postDict = Dictionary<String,AnyObject>()
postDict["pass"]=123
postDict["name"]="ali"
postDict["surname"]=NSNull()
let jsonData = NSJSONSerialization.dataWithJSONObject(postDict, options: NSJSONWritingOptions.allZeros, error: nil)!
let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)!
// -> {"pass":123,"surname":null,"name":"ali"}
postDict[surname] = Optional<AnyObject>(nil)
You can use the Optional type
var postDict = ["pass": 123, "name": "ali", "surname": Optional()]
Below dictionary will hold one key with nil value
var dict = [String:Any?]()
dict["someKey"] = nil as Any?
To add a nil value to a dictionary in Swift, your dictionary's values must be of the Optional type.
Consider a Person class:
class Person {
let name: String
weak var spouse: Person?
init(name: String, spouse: Person?) {
self.name = name
self.spouse = spouse
}
}
Instances of the Person type can have a name and an optional spouse. Create two instances, and add the first to a dictionary:
let p1 = Person(name: "John", spouse: nil)
let p2 = Person(name: "Doe", spouse: p1)
p1.spouse = p2
var people = [p1.name: p1.spouse]
This dictionary (called people) maps names to spouses, and is of type [String: Person?]. You now have a dictionary with a value of Optional type: Person?.
To update the value of the key p1.name to be nil, use the updateValue(_: forKey:) method on the Dictionary type.
people.updateValue(nil, forKey: p1.name)
people[p1.name]
The value for the key p1.name is now nil. Using updateValue(_: forKey:) is a bit more straightforward in this case because it doesn't involve making a throwaway instance, setting it to nil, and assigning that instance to a key in a dictionary.
NB: See rintaro's answer for inserting null into a post's dictionary.
var dict = [Int:Int?]()
dict[0] = (Int?).none // <--- sets to value nil
dict[0] = nil // <-- removes
dict[0] = .none // <-- same as previous, but more expressive
switch dict[0] {
case .none:
Swift.print("Value does not exist")
case .some(let value):
if let value = value {
Swift.print("Value exists and is", value)
} else {
Swift.print("Value exists and is nil")
}
}
postDict[surname]=nil
When you use subscript to set nil. It deletes the key if exists. In this case key surname will be removed from dictionary if exists.
To set value as nil, there are certain ways.
postDict.updateValue(nil, forKey: surname)
or
let anyObjectNil : AnyObject? = nil
postDict[surname] = anyObjectNil
or
postDict[surname] = nil as AnyObject?