NSUserDefaults optionals in Swift - ios

I'm trying to deal with compiler in case of optional values. Task is very simple, my func fetches user defaults to appear them in tableview. If user launches app for the first time, it setts default values. Setting default values works fine (checked with print log), but fetching causes:
fatal error: unexpectedly found nil while unwrapping an Optional value
I'm already worked with optionals for a long time, but, perhaps I'm still confused about them, 'cos I see that everything seems to be correct and even compiler says, that everything is ok.
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
var defsArray = [String]()
if (userDefs.objectForKey("gender") != nil) {
defsArray.append((userDefs.objectForKey("gender")?.stringValue)!)
defsArray.append((userDefs.objectForKey("age")?.stringValue)!)
defsArray.append((userDefs.objectForKey("online")?.stringValue)!)
}
else {
userDefs.setObject("Male", forKey: "gender")
userDefs.setObject("21-30", forKey: "age")
userDefs.setObject("Online", forKey: "online")
}
return defsArray
}

You are force unwrapping your optionals, and you should get them as strings before appending them to your array.
A cleaner way to set the defaults would be to coalesce the unwrapping of your optionals, Try the following approach:
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
var defsArray = [String]()
defsArray.append(userDefs.stringForKey("gender") ?? "Male")
defsArray.append(userDefs.stringForKey("age") ?? "21-30")
defsArray.append(userDefs.stringForKey("online") ?? "Online")
return defsArray
}
The code above uses the coalesce (??) operator. If your optional, say userDefs.stringfForKey("gender"), returns nil, the coalesce operator will use the default value "Male".
Then at a later time you can save your user defaults (or create them) if they haven't been set before.
Also, is worth noticing that you should be unwrapping your optionals using the if let notation. Instead of comparing if its != nil, as this will prevent you from force unwrapping them inside the code block.
I hope this helps!

Apple highly recommends to set default values via registerDefaults of NSUserDefaults.
As soon as possible (at least before the first use) set the default values for example in applicationDidFinishLaunching:
let userDefs = NSUserDefaults.standardUserDefaults()
let defaultValues = ["gender" : "Male", "age" : "21-30", "online" : "Online"]
userDefs.registerDefaults(defaultValues)
The default values are considered until a new value is set.
The benefit is you have never to deal with optionals nor with type casting.
func getFiltersSetts() -> [String] {
let userDefs = NSUserDefaults.standardUserDefaults()
return [userDefs.stringForKey("gender")!,
userDefs.stringForKey("age")!,
userDefs.stringForKey("online")!]
}
The forced unwrapping is 100% safe because none of the keys can ever be nil.
Please read Registering Your App’s Default Preferences in Preferences and Settings Programming Guide

You can create a default key "firstBootCompleted". Then use:
let defaults = NSUserDefaults.standardUserDefaults()
if (defaults.boolForKey("firstBootCompleted") == false) {
defaults.setObject(true, forKey: "firstBootCompleted")
// Initialize everything for your first boot below
} else {
// Initialize everything for not your first boot below
}
The reason this works is that boolForKey returns false when the object for key is nil

You are storing all the value as String and then at the time of fetching
you are typecast the value again in String
userDefs.objectForKey("gender")?.stringValue
A String can not be convert as String again.
try to access as
userDefs.stringForKey("gender")

Related

Access Optional property in multiple function for calculations - Swift

I have a NSObject Subclass. Say CityWalks
class CityWalks{
var totalCount:Int?
}
How do I use this property further? Should I check the nil coalescing every time this value is accessed.
example:
let aObject =
say in one fucntion (function1()) , I need to access this value, then it would like (aObject!.totalCount ?? 0)
func function1(){
...Some Access code for the object....
(aObject!.totalCount ?? 0)
}
Similarly in every other function(function2()) , I will have to write the same code.
func function2(){
...Some Access code for the object....
(aObject!.totalCount ?? 0)
}
So, what could be a better approach for such field, considering this property might receive a value from server or might not.
If you have a default value for this property just assign this value as default value.
class YourClass {
var totalCount = 0
}
I'd recommend you avoid using an optional value if it's possible. Because optional values its a first place when you can get an error.
As stated in the comments and the other answer using an optional is not really optimal in your case. It seems like you might as well use a default value of 0.
However, to clarify, you have to check the value when unwrapping the optional.
Sometimes it's possible to pass an optional to UIElement etc and then you don't really need to do anything with them
There are pretty ways of checking for nil in optional values built into swift so you can build pretty neat code even though you work with optional.
Look in to guard let and if let if you want to know more about unwrapping values safely.
if let
if let totalWalks = aObject?.totalCount {
//operate on totalWalks
}
guard
guard let totalWalks = aObject?.totalCount else { return }
//operate on totalWalks
There are also cases where you will want to call a function on an optional value and in this case you can do so with ?
aObject?.doSomething()
Any return values this function might have will now be wrapped in an optional and you might have to unwrap them as well with an if let or guard
When working with optionals you should try to avoid forcing the unwrap with ! as even though you at the moment know that the value is not null that might after a change in the code not be true anymore.

NSUserDefaults boolForKey

I'm trying to get NSUserDefaults to work in my app. The code below is supposed to check if there is a bool value in the NSUserDefaults called "iCloudOn". If there is, it then assigns the value of a UISwitch to the NSUserDefault. If there is not, it goes ahead and assigns false to the NSUserDefault.
I have marked the line that I am getting the error on. The error I receive is "Bound value in a conditional binding must be of Optional type." I can't figure out why I am getting this error and what I need to do to make this work. Can anyone help shed some light?
class SettingsTableViewController: UITableViewController{
#IBOutlet weak var iCloudUISwitch: UISwitch!
let appSettings = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
//THIS IS THE LINE I AM GETTING AN ERROR ON
if let iCloudOn = appSettings.boolForKey("iCloudOn") {
//iCloud is on
iCloudUISwitch.on = appSettings.boolForKey("iCloudOn")
}
else {
//Nothing stored in NSUserDefaults yet. Set a value.
appSettings.setValue(false, forKey: "iCloudOn")
}
}
The function boolForKey does not return an optional. It always returns true or false. If the key doesn't exist in user defaults, it returns false.
You should use objectForKey, which returns AnyObject?, then cast it to a Bool.
Edit:
If this function was being written today for Swift it would almost certainly return a Bool? type (Optional Bool) This would be a perfect use-case for an optional. However, NSUserDefaults was defined and written a LONG time before Swift (It was part of NextStep)
You can just use appSettings.boolForKey('iCloudOn') without the if let to get your value. According to the documentation false is returned if no value is associated to the key.
NSUserDefaults.boolForKey

NSUserDefaults properly storing Strings in SpriteKit Swift

So I set up a NSUserDefault to store a string in my GameViewController
NSUserDefaults.standardUserDefaults().setObject("_1", forKey: "SkinSuffix")
The idea is it stores a suffix which I will attach to the end of an image name in order to save what skin of a character the player should use.
When I call the value in my GameScene class like so
var SkinSuffix = NSUserDefaults.standardUserDefaults().stringForKey("SkinSuffix")
println(SkinSuffix)
it prints "Optional("_1")" instead of just "_1" so when I try to change the name of my image file like so, it doesn't load the image file
hero = SKSpriteNode(texture: heroAtlas.textureNamed("10Xmini_wizard\(SkinSuffix)"))
How do I fix this issue?
You can unwrap the String using the Optional Binding construct. This avoids a crash of the app if the value is nil.
if let skinSuffix = NSUserDefaults.standardUserDefaults().stringForKey("SkinSuffix") {
println(skinSuffix)
}
Update: As correctly suggested in the comment below, I am putting the retrieved value in a constant (let). We should always use constants when we don't need to change the value. This way the Swift compiler can make some optimizations and does prevent us from changing that value.
That's because it's implicitly an optional not of type String. You need to case it as such or unwrap the optional in your println statement.
var SkinSuffix = NSUserDefaults.standardUserDefaults().stringForKey("SkinSuffix") as! String
Or in your println: println(SkinSuffix!)
As a side note, you should you camelCase for your variable names.
You can use "??" Nil Coalescing Operator to help you dealing with nil and it also
allows you to specify a default string value.
NSUserDefaults().setObject("_1", forKey: "SkinSuffix")
let skinSuffix = NSUserDefaults().stringForKey("SkinSuffix") ?? ""
println(skinSuffix) // "_1"

Swift Optionals - Unexpectedly found nil when unwrapping an optional value

I'm new to Swift and have been trying to wrap (ha) my head around optional values. As far as I can see - although I'm probably wrong - variables can be optional and therefore contain a nil value and these have to be accounted for in code.
Whenever I hit the 'save' button in my application I get the error: 'fatal error: unexpectedly found nil while unwrapping an Optional value'.
#IBAction func saveTapped(sender: AnyObject) {
//code to save fillup to Parse backend
// if variable is not nil
if let username = PFUser.currentUser()?.username {
// set username equal to current user
self.object.username = username
}else{
println("PFUser.currentUser()?.username contained a nil value.")
}
// if variable is not nil
if let amount = self.amountTextField.text {
//set amount equal to value in amountTextField
self.object.amount = self.amountTextField.text
}else{
println("self.amountTextField.text contained a nil value.")
}
// if variable is not nil
if let cost = self.costTextField.text {
// set cost equal to the value in costTextField
self.object.cost = self.costTextField.text
}else{
println("self.costTextField.text contained a nil value.")
}
// set date equal to the current date
self.object.date = self.date
//save the object
self.object.saveEventually { (success, error) -> Void in
if error == nil {
println("Object saved!")
}else{
println(error?.userInfo)
}
}
// unwind back to root view controller
self.navigationController?.popToRootViewControllerAnimated(true)
}
Not sure if the error is because of something in this block of code or somewhere else - can provide the main class code if needed.
Any help anyone can provided would be really appreciated as this has been bugging me for a while now.
From your code and the comments it sounds like your issue definitely lies with self.object
Your code never uses an if let statement to check to ensure self.object is not nil
Using println(username) works because your username is not nil. But when you try to call self.object.username, it's the self.object that is causing the crash.
You may have a property in your implementation like var object:CustomPFObject! which means, the first time you access this variable it's expected to not be nil. You'll probably want to check the code where you are setting self.object for the first time, and to make sure that it's being set before you've tried to access it.
If you're not able to manage when self.object is set, and when it's accessed, then change your declaration to var object:CustomPFObject? Now it's an optional, and as you write your code you'll be forced to make decisions as you go along.
For example:
var object:CustomPFObject?
let text = self.object.username //won't compile
let text = self.object!.username //mighty crash
let text = self.object?.username //you're safe, 'text' just ends up nil
I hope this helps you solve your issue.

Unwrapping NSMutableArray from NSUserDefaults

I am trying to unwrap a NSMutableArray from user defaults, but keep getting the error unexpectedly found nil while unwrapping an Optional value. I tried to check for nil after getting the array, but it is happening on the line that is getting the array from the UserDefaults, so I don't know how to fix this error. Can anyone help?
var classURLs: NSMutableArray = NSMutableArray();
let defaults: NSUserDefaults = NSUserDefaults(suiteName: "group.myCompany.Array")!;
classURLs = NSMutableArray(object: defaults.objectForKey("Class URLs")!);
NSUserDefaults.objectForKey returns an optional for a reason – if the key isn’t present, you get nil back. You shouldn’t just force-unwrap it with !
The most common case is this is the first time you’ve tried reading it without having ever written it.
In which case you probably want a default value, perhaps an empty array:
let classURLs = defaults.stringArrayForKey("Class URLs") as? [String] ?? []
(?? substitutes the value on the right if the value on the left is nil)
Note, it’s probably better if you’re writing Swift to go with a Swift array (e.g. [String]) rather than NSMutableArray, unless all you are going to do is pass it straight into a call to Objective-C.
You can also avoid the ! you’re using with the NSUserDefaults init by using optional chaining:
let defaults = NSUserDefaults(suiteName: "group.myCompany.Array")
var classURLs = defaults?.stringArrayForKey("Class URLs") as? [String] ?? []
If the key does not exist, the forced unwrap (!) of the nil object will result in a crash. You need to account for this case.
var classURLs = NSMutableArray(object:
NSUserDefaults.standardUserDefaults().objectForKey("foo") ?? [])
NB: transitioning from Objective-C? You don't need the semicolons! ;-)

Resources