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
Related
I'm trying to extract the earliest (and latest, but since the two methods are going to be nearly identical, I'll concentrate on "earliest" for this question) date held in a global DataSource object, theData, and return it as a NON-OPTIONAL value.
DataSource is a "souped up Array" object holding an arbitrary number of DataEntry objects. Here's the bare-bones of the definition of DataEntry:
class DataEntry: Codable, Comparable {
var theDate: Date = Date() // To avoid using an optional but still silence compiler
// complaints about no initializer
// Lots of other properties and support code irrelevant to the question snipped for brevity.
}
Retrieving the needed date(s) is done as a method of my DataSource class:
class DataSource: Codable {
var theArray: [DataEntry] = Array()
// Several hundred lines of non-relevant support and convenience code chopped
// Return either .theDate from the earliest DataEntry held in theArray, or Date()
// if theArray.first hands back a nil (indicating theArray is unpopulated).
// Either way, I specifically want the returned value to *NOT* be an optional!
func earliest() -> Date {
// First, make certain theArray is sorted ascending by .theDate
theArray.sort {
$0.theDate < $1.theDate
}
// Now that we know theArray is in order, theArray.first?.theDate is the earliest stored date
// - If it exists.
// But since .first hands back an optional, and I specifically DO NOT WANT an optional return from
// .earliest(), I nil-coalesce to set firstDate to Date() as needed.
let firstDate: Date = theArray.first?.theDate ?? Date()
print("firstDate = \(firstDate)")
return firstDate
}
func latest() -> Date {
// gutted for brevity - mostly identical to .earliest()
return lastDate
}
}
Note that I take pains to make sure what gets returned is not an optional. And, in fact, later code blows up if I try to treat it like it could be:
class SelectDateRangeViewController: UIViewController {
#IBOutlet weak var startPicker: UIDatePicker!
#IBOutlet weak var endPicker: UIDatePicker!
// Irrelevant code elided
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let e = theData.earliest()
// Note: Changing the previous line to
// let e = theData.earliest()!
// gets a compile-time error: "Cannot force unwrap value of non-optional type 'Date'"
let l = theData.latest()
// We should now have the earliest and latest dates in theData held in e and l, respectively.
// Print them to the debugger console to verify they're actually what I expect...
print ("e = \(e)")
print ("l = \(l)")
// ...and carry on
startPicker.minimumDate = e // <--- Crashes here, claiming that I've tried to implicitly unwrap an optional value! See the console log dump below
startPicker.maximumDate = l
startPicker.date = e
endPicker.minimumDate = e
endPicker.maximumDate = l
endPicker.date = l
}
}
What follows is what I see in the debugger console when I try to run the "actual code" (rather than the gutted-to-save-space version presented here)
---start debugger log
firstDate = 2020-04-16 15:00:31 +0000 <---- This line and the next come from inside the .earliest()/.latest() methods of DataSource
lastDate = 2020-04-27 15:43:23 +0000
e = 2020-04-16 15:00:31 +0000 <---- And these two lines come from the viewWillAppear() method shown above.
l = 2020-04-27 15:43:23 +0000
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /ActualPathRemoved/SelectDateRangeViewController.swift, line ##
--- end debugger log
Where the ## in that crash line is the actual line number occupied by the "startPicker.minimumDate = e" statement in the "all of the code is there" version.
The values displayed as "firstDate", "lastDate", "e", and "l" are ABSOLUTELY CORRECT for the test dataset I'm using.
But wait a minute! The compiler says that the return from .earliest() is not an optional when I try to force-unwrap it! And since when is a perfectly valid Date object "nil"??? And why is the code trying to "implicitly unwrap" a non-nil, not-optional value?!?!? Somebody make up my mind!
So what am I misunderstanding? Either my code is hosed, or what I understood while trying to write it is. Which is the case, and what's the problem???
Answer to #Paulw11's comment query:
#IBAction func reportRequestButtonAction(_ sender: Any) {
let theVC = SelectDateRangeViewController()
self.navigationController?.pushViewController(theVC, animated: true)
}
This bit of code lives in the viewController that offers the button as part of a scene that offers a menu of "let the user tinker with the dataset in various ways" buttons. When first added, I intended to bring up the SelectRangeScene with a segue, but for some reason, decided that pushing it onto the UINavigationController stack would be a better way of doing it, though I can't remember why it was I thought that now - the reason apparently got shaken loose and lost while I was beating my head against the problem of why trying to set the datepickers was crashing.
If the code crashes in this line then the startPicker outlet is not connected.
It has nothing to do with the dates, they are definitely non-optional.
You are creating your new view controller instance with
let theVC = SelectDateRangeViewController()
Since you don't get the instance from the storyboard, none of the outlets are bound; they will be nil. As your outlets are declared as implicitly unwrapped optionals, you get a crash when you refer to them.
You could instantiate the new view controller from your storyboard but it would probably be simpler to use a segue either directly from your button or by using performSegue in your #IBAction
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")
I am trying to add names into a UITableView. The names will be stored as a NSUserDefault.
My viewDidLoad:
var PlayersUserDefault = NSUserDefaults.standardUserDefaults()
if (PlayersUserDefault.arrayForKey("playersKey") != nil){
players = PlayersUserDefault.arrayForKey("playersKey")
}
Add name button:
#IBAction func addButtonAction(sender: AnyObject) {
players!.append(namesTextBox.text)
myTableView.reloadData()
}
When I press the addButtonAction, I am getting error on following:
players!.append(namesTextBox.text)
Players declaration:
var players = NSUserDefaults().arrayForKey("playersKey")
Any idea why I am getting error?
Error image:
In one line you use NSUserDefaults.standardUserDefaults() and in the other you create new instance of NSUserDefaults and ask for "playersKey". What you should do is to use NSUserDefaults.standardUserDefaults() for retrieving AND saving your players.
Take a look at the refence:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/#//apple_ref/doc/constant_group/NSUserDefaults_Domains
For init:
This method does not put anything in the search list. Invoke it only
if you’ve allocated your own NSUserDefaults instance instead of using
the shared one.
and for shared instance:
If the shared defaults object does not exist yet, it is created with a
search list containing the names of the following domains, in this
order:
NSArgumentDomain, consisting of defaults parsed from the application’s
arguments
A domain identified by the application’s bundle identifier
NSGlobalDomain, consisting of defaults meant to be seen by all
applications
Separate domains for each of the user’s preferred languages
NSRegistrationDomain, a set of temporary defaults whose values can be
set by the application to ensure that searches will always be
successful
The defaults are initialized for the current user. Subsequent
modifications to the standard search list remain in effect even when
this method is invoked again—the search list is guaranteed to be
standard only the first time this method is invoked.
The players array is nil at the point of time thats why the array is not unwrapped. If you tap the button at the first time, your UserDefaults will not be having players, So the players array is nil. So you can solve this in two ways do like this.
On your viewDidLoad()
var PlayersUserDefault = NSUserDefaults.standardUserDefaults()
if (PlayersUserDefault.arrayForKey("playersKey") != nil){
players = PlayersUserDefault.arrayForKey("playersKey")
}else {
players = [String]()
}
Otherwise, initialise the players array with an empty string array like this:
var players = [String]()
This may help you.
You could try unwrapping the value of players before attempting to do append. Then you will know for sure if it's nil or not.
Try:
#IBAction func addButtonAction(sender: AnyObject) {
if let playerValue = players {
players!.append(namesTextBox.text)
myTableView.reloadData()
}else{
println("players is nil!")
}
}
How can I change the boolean value of my Parse class named "shoppingCart" by clicking a button?
#IBAction func addToCart(sender: AnyObject) {
var cartObjectStatus = itemObject.valueForKey("shoppingCart") as! Bool!
cartObjectStatus = false
}
itemObject.setBool(false, forKey: "shoppingCart")
The problem with your original attempt is that you're setting the value of the local variable to false. Using the above setBool method will alter the object's variable.
Edit - If PFObject doesn't support setBool, you'll have to go with setValue:forKey:
itemObject.setValue(false, forKey: "shoppingCart")
This is a method on NSObject, which PFObject has to derive from.
You need to call setValueForKey on the itemObject. When you get the bool with valueForKey it's passed by value, not reference, so changing it like you are will not update itemObject's version of it.
I suggest reading both Parse documentation and some tutorial on Swift basics.
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.