iphone dev - NSUserDefaults check for boolean existence - iphone

I've just added a settings bundle to my app and am having trouble reading the bool settings. I know that upon launch of the app, the settings are not read unless the user actually enters them - and that's what I am trying to capture.
However, my code is simply capturing if the answer is NO OR they havent been set. I need to find out if they've been set, THEN set answers!
setting code:
BOOL playSound;
BOOL playVibrate;
//test for some defaults
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if (![prefs boolForKey:#"pref_sound"]) {
playSound = YES;
playVibrate = YES;
} else {
playSound = [prefs boolForKey:#"pref_sound"];
playVibrate = [prefs boolForKey:#"pref_vibrate"];
}
if (playSound) {
//do stuff
}
the problem is, if the user sets the settings to "NO", the code then changes both vibrate AND sound to yes - which is meant to be the capture for NOT setting....
any ideas?

First of all you have a bug in your if conditional. You're checking the boolean value itself which, according to the boolForKey: documentation, will return a NO if it's not set yet. So boolForKey: is not the right way to do that.
Here's two other ideas.
Consider using another setting with another key to specify whether your settings have been initialized. Check it when you launch your app, or when you first read a setting. Initialize if needed. For instance:
- (void) initializeUserDefaults {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (nil == [defaults objectForKey:#"initialized_defaults"]) {
[defaults setBool:YES forKey:#"pref_sound"];
[defaults setBool:YES forKey:#"pref_vibrate"];
[defaults setObject:#"dummy_value" forKey:#"initialized_defaults"];
}
}
A simpler solution (but I'm not sure if this would work) would be to change your conditional to read:
if (![prefs objectForKey:#"pref_sound"]) {
Again I don't know if this will do what I imagine it will, but I imagine that objectForKey: will return the underlying boxed boolean object, if it's there, or nil.
If you add a new setting in a new version of your app, you don't want to leave your new settings uninitialized and you don't want to stomp your users' existing settings. This second method makes that effortless, and in the first method it's easier to screw up. But the first method also gathers all your settings in one place so you can see how it's supposed to work, which I like.
I am not sure what ADC docs would consider a best practice though. To find that out, I'd suggest you look at any code samples referenced from the NSUserDefaults class reference.

maybe the best idea is to register some defaults for the NSUserDefaults using
(void)registerDefaults:(NSDictionary *)dictionary
see Apple Documentation at
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/index.html#//apple_ref/doc/uid/20000318-SW5
To come back to your original problem: in the past I was using NSUserDefaults.dictionaryRepresentation in order to check the existence of a key:
BOOL playSound;
BOOL playVibrate;
//test for some defaults
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if (nil == [prefs dictionaryRepresentation]["pref_sound"]) {
playSound = YES;
playVibrate = YES;
} else {
playSound = [prefs boolForKey:#"pref_sound"];
playVibrate = [prefs boolForKey:#"pref_vibrate"];
}
if (playSound) {
//do stuff
}
Hope this works for you.

Kevin Conner's solution works for me, but there's another thing I think I should point out.
My app has a preference for handedness with right-handed as the default. When I hit run in Xcode, use the app to change my preference to left-handed, and then run it a second time, my preference is still right-handed.
It seems that running in Xcode erases settings. I decided to manually quit the app from inside the simulator, open it again from inside, and then I saw my new preferences.

Related

In a Swift macOS app, what would be an elegant solution for implementing a window with choices bound to UserDefaults that has a Cancel Button?

I have a Cocoa Swift macOS app, not document based. I have to present some persistent choices to the user, and I would like to use NSUserDefaults and bindings. Everything is fine, except that in this options sheet window I have a "Cancel" button. I would expect that the changes made to the settings (for example a checkbox) could not persist if the user has pressed the "Cancel" button, but I can't imagine a possible elegant implementation to make this happen. When using Core Data, this scenario is very well handled using a subcontext, but in my case I would like to avoid Core Data because I just have a few simple options. I hope there is a technique that I am missing that can solve the problem. Any help is much appreciated. Thanks for the attention.
I have found a working, perhaps not so elegant implementation:
- (IBAction)userDidPressSetMapOptions:(NSButton *)sender {
NSStoryboard *mapOptionsStoryboard = [NSStoryboard storyboardWithName:#"MapOptions" bundle:nil];
self.mapOptionsWindowController = [mapOptionsStoryboard instantiateInitialController];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDictionary *currentDict = [userDefaults dictionaryRepresentation];
[self.view.window beginSheet:self.mapOptionsWindowController.window completionHandler:^(NSModalResponse returnCode) {
if (returnCode == NSModalResponseCancel) {
dispatch_async(dispatch_get_main_queue(), ^{
[currentDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[userDefaults setValue:obj forKey:key];
}];
});
}
}];
}
Nevertheless, I still would like a better answer. Thank you for your attention.
BTW: I have used dispatch_async because otherwise the checkboxes, for example, turn back to their previous setting before the window closes after a cancel, which is confusing.

How to save the subView that has been added with NSUserDefaults?

I'm working in a book app, and I want to use a page marker to help the user remember where he stopped reading. So I have added a bar button with an image ("mark.png"). A mark view will be added to the chapter view when it is tapped, and if it is been tapped again the mark will be removed from the superView. I'm using this code:
- (void)showMark {
if (![markView superView]) {
[chapterOne addSubView:markView];
}
else {
[markView removeFromSuperView];
}
}
It is working fine but ever time I exit the app and rerun again the mark view is gone, so how can I keep it?
I found some forums talking about the NSUserDefaults to save actions but I really don't know how to use it with my code. Any help will be appreciated.
You can't use NSUserDefaults to save entire views, but you can save the parmeters that would help determine where the bookmark should be set.
For example if you are basing the book mark by a page number you could save the page to the NSUserDefaults when the user leaves the view controller.
Example:
[[NSUserDefaults standardUserDefaults] setInteger:23 forKey:#"bookMarkPage"];
[[NSUserDefaults standardUserDefaults] synchronize];
When the user comes back the the view controller you can check if there is a bookmark:
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"bookMarkPage"] != nil) {
int pageNumber = [[NSUserDefaults standardUserDefaults] objectForKey:#"bookMarkPage"];
[self setBookmarkForPage:pageNumber];
}
Possible bookmark construction method:
- (void) setBookmarkForPage:(Int)pageNumber {
// run through the logic of placing the bookmark on the correct page
}
You can use whatever parameters you need to determine where to place the book mark. When a user originally places the bookmark what parameters you use to figure out where to place the bookmark? Try to use the same logic for when a user first places the bookmark.
I don't know exactly what you want to save, but you can just about any kind of data with NSUserDefaults, like this:
[[NSUserDefaults standardUserDefaults] setInteger:123 forKey:#"CurrentPageNumber"];
When you have set all the values you need, save them:
[[NSUserDefaults standardUserDefaults] synchronize];
Then when app opens, check to see if the value is set. If it is draw your marker.
if ([defaults valueForKey:#"CurrentPageNumber"] != nil) {
int pageNumber = [defaults valueForKey:#"CurrentPageNumber"]
if (pageNumber == 1) {
[chapterOne addSubView:markView];
}
else {
[markView removeFromSuperView];
}
}
The other answers state great ways to work around the issue. Just to clarify, UIView or any of the derivatives are not supported for NSUserDefaults. NSUserDefaults allows just primitive object types (NSString, NSNumber, NSArray, and NSDictionary). There might be one or two I missed. But UIView or UIViewController object types can't be saved in NSUserDefaults.

Implement App Instructions on First Start Up

I have a couple of images that I would like to display the first time a user runs the app to show the user how to effectively use the app. What is the best method to do this sort of thing?
Thanks
The easiest way is to set a flag that says you have shown the app instructions. You can store them in user defaults.
So you would put something like this in your app delegate.
static NSString* const kAppHasShownStartupScreen = #"kAppHasShownStartupScreen";
BOOL hasShownStartup = [[NSUserDefaults standardUserDefaults] boolForKey:kAppHasShownStartupScreen];
if(hasShownStartup)
{
window.rootViewController = //your normal startup view controller
}
else
{
window.rootViewController = //your new view controller with instructions
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kAppHasShownStartupScreen];
}
Create some boolean values to store in NSUserDefaults or in Core Data that represent whether or not the user has viewed the 'tutorial'. Show the images by loading them into UIImageView's and adding them as subviews if the flag is false. Set the flag to true after they have viewed the images.
You can accomplish this functionality by using a BOOL value in NSUserDefaults:
#define appdb ((NSUserDefaults *)[NSUserDefaults standardUserDefaults])
if(![appdb boolForKey:#"applicationHasRunBefore"]) {
[appdb setBool:YES forKey:#"applicationHasRunBefore"];
[appdb synchronize];
...
// Do the tutorial
}

iPhone - How to detect if a key exists in NSUserDefaults standardUserDefaults

Using NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];,
I use calls like BOOL boolFromPrefs = [defaults boolForKey:#"theBoolKey"]; To get a saved BOOL value.
If the key is not found, NO is returned (default behaviour of boolForKey).
But... NO can be a saved setting. Same thing for intForKey
So how can I test if a key exists before trying to get its value ?
Do it the right way and register default values.
NSDictionary *userDefaultsDefaults = #{
#"SomeKey": #NO,
#"AnotherKey": #"FooBar",
#"NumberKey": #0,
};
[NSUserDefaults.standardUserDefaults registerDefaults:userDefaultsDefaults];
do this before you use anything from NSUserDefaults. The beginning of application:didFinishLaunchingWithOptions: is a safe place.
You have to register the defaults each time the app launches. NSUserDefaults only stores values that have been explicitly set.
If you use default values you don't have to use a check for a "isFirstLaunch" key, like suggested in other answers.
This will help you when you roll out an update and you want to change the default value for a NSUserDefaults item.
Check if the object exists before conversion to a BOOL.
if ([defaults objectForKey:#"theBoolKey"] != nil) {
boolFromPrefs = [defaults boolForKey:#"theBoolKey"];
} else {
boolFromPrefs = DEFAULT_BOOL_VALUE;
}
I did it this way:
NSUserDefaults *prefs = NSUserDefaults.standardUserDefaults;
if ([[prefs dictionaryRepresentation].allKeys containsObject:#"yourKey"]) {
float yourValue = [prefs floatForKey:#"yourKey"];
}
You can test by using objectForKey: and if that is nil then it is not set. All boolForKey does it takes the NSNumber returned if any and returns a BOOL value.
I would recommend setting default values for any key that your application might use. You could do this in the application:didFinishLaunchingWithOptions: method. That way you will know that each value has been set.
Hint, set a key called "defaultsSet" to YES so that you only do this once. Also, remember to call [[NSUserDefaults standardUserDefaults] synchronize] to save the values.
Swift Example based on MarkPowell's answer
if (NSUserDefaults.standardUserDefaults().objectForKey("SomeBoolSetting") == nil) {
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "SomeBoolSetting")
println("Bool WAS nil")
} else {
var boolValue:Bool? = NSUserDefaults.standardUserDefaults().boolForKey("SomeBoolSetting")
println("Bool WAS NOT nil \(boolValue)")
}
swift version of #Tim Autin 's answer:
if contains(NSUserDefaults.standardUserDefaults().dictionaryRepresentation().keys.array, "chuiser_cook_orders_dirty") {
println("exist")
}
Dont Complicate it :
if([NSUserDefaults.standardUserDefaults objectForKey:#"yourBoolKey"])
{
// Object Already Stored in User defaults
}
else
{
//Object not stored
}

Checking that you NSUserDefaults has valid data

I load my data from NSUserDefaults and it works well.
But I'm a little concerned that the first time the app will be fun and there is no saved data something strange might happen. Is there a way to check that I am not getting back nil values or some kind of standard query to see what you are getting back is a valid answer?
Would putting a
ctCountry.isoCountryName = [prefs objectForKey:#"ctCountry.isoCountryName"];
if (ctCountry.isoCountryName == nil)
{
//code to handle noobject returned
}
be a valid and solid way to handle this kinda of work?
Current Code. No checks for data validity.
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
ctCountry.isoCountryName = [prefs objectForKey:#"ctCountry.isoCountryName"];
ctCountry.isoCountryCode = [prefs objectForKey:#"ctCountry.isoCountryCode"];
ctCountry.isoDialingCode = [prefs objectForKey:#"ctCountry.isoDialingCode"];
Thanks
-Code
Yes, checking for nil works. Better yet, you could register default values by passing a dictionary of the default preferences to -[NSUserDefaults registerDefaults] at every app launch.
Yes. It is not possible to store nil, so the only time ctCountry.isoCountryName == nil is when the corresponding preference does not exist.