NSUserDefaults failing to persist across app shutdown/startup - objective-c

EDIT: Code updated to reflect Paul.s's suggestions.
Have been scouring SO for the past week and still cannot get my app to persist the changes to settings from within the app across a shutdown/restart of the app in both Sim and Device.
Trying to use a NSMutableDictionary from a Singleton, but I don't think this is the problem.
a) Set initial default values for the app using registerDefaults in AppDelegate's applicationDidFinishLaunchingWithOptions:
AppManager *global = [AppManager sharedInstance];
// set up the defaults.
global.gWantFoo = YES; // This is a BOOL which relates to a UISwitch
// #define kWantFoo #"gWantFoo"
global.globalSettingsDict = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:
[NSNumber numberWithBool:global.gWantFoo], kWantFoo, nil];
global.globalSettings = [NSUserDefaults standardUserDefaults];
[global.globalSettings registerDefaults:global.globalSettingsDict];
[global.globalSettings synchronize];
b) In the ConfigView where I am able to set the switch, I have the a selector execute the following on a switch change.
self.global.gWantFoo = NOT(self.global.gWantFoo); // #define NOT(a) !(a)
[self.global.globalSettings setBool:self.global.gWantFoo forKey:kWantFoo];
[self.global.globalSettings synchronize];
c) I have a synchronize in applicationDidEnterBackground and friends, and while the switch value does keep the change for the duration of the app instance's life, once I restart, gWantFoo is overwritten with the default of "YES".
Any advice is appreciated. I'm on the verge of defenestrating my ailing macbook pro with a hail of expletives following it closely behind. Ok, i'm calmer for this edit. I feel like gently shaking the macbook pro to demonstrate that i'm less than pleased with NSUserDefaults :-)
Cheers
sc.

So from start to finish this is what you need to do/what should be happening
In application:didFinishLaunchingWithOptions: one of the first things you do is register defaults like this:
NSDictionary *defaultsDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:NO] , PSWantFoo, nil];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults registerDefaults:defaultsDefaults];
Note: PSWantFoo is defined as NSString * const PSWantFoo = #"PSWantFoo"; so i don't have string literals littered everywhere.
At this point if the app has never been run NSLog(#"%d", [defaults boolForKey:PSWantFoo]); it will print 0.
Now when I want to set the value I use something like:
[defaults setBool:YES forKey:PSWantFoo];
[defaults synchronize];
Now when I run NSLog(#"%d", [defaults boolForKey:PSWantFoo]); it will print 1.
As for persistance at this point as I have actually set a value a plist is created for me at
<path to app>/Library/Preferences/<bundle identifier>.plist
If you inspect this file you will see something like
<?xml version="1.0" encoding="UTF-8"?>
<DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PSWantFoo</key>
<true/>
</dict>
</plist>
Now from this point on this value will be read as opposed to the one registered in registerDefaults:
Update
Why have you hardcoded this in this order?
// set up the defaults.
global.gWantFoo = YES; // This is a BOOL which relates to a UISwitch
Why not do it the other way round?
Register defaults with default value YES;
NSDictionary *defaultsDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kWantFoo, nil];
[defaults registerDefaults:defaultsDefaults];
and then after that you get the setting
global.gWantFoo = [defaults boolForKey:kWantFoo];
This way if the app has not yet set the preference the answer will be YES otherwise it will be what the app has previously set it to.

You should set the flag #"gSettingsAreSet" after defaults values are saved
[[NSUserDefaults standardUserDefaults] setObject:#"YES" forKey:#"gSettingsAreSet"];
[[NSUserDefaults standardUserDefaults] synchronize];

Related

How to change the size of the system cursor in a macosx cocoa app programmatically using swift or objective-c?

I've tried to set it's global size using this code:
-(void)setOption {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *olddict = [defaults persistentDomainForName:#"com.apple.universalaccess"];
NSMutableDictionary *newdict = [olddict mutableCopy];
[newdict setObject:#4.0 forKey:#"mouseDriverCursorSize"];
[defaults setPersistentDomain:newdict forName:#"com.apple.universalaccess"];
[defaults synchronize];
NSLog(#"Cursor size set to %#", newdict);
}
And I can see in the NSLog that it changed it but I don't know how to restart/reset the system cursor in order for the cursor to change to the specified size.
Does anybody know a better way to change it's size programmatically or how to restart the system cursor after the defaults change?
EDIT(about duplication): My question is unique because I can't use applescript in resolving this like the answer provided in the other topic. Also the topic has been created in 2013, and seems outdated. Maybe things have changed a little since then. Also maybe Swift would be a viable solution for resolving this. Who knows? All these arguments make it clear that this is not a duplicate question.
CGError state = CGSShowCursor(CGSDefaultConnection) ;
if (state != kCGErrorSuccess) NSLOG(#"error : %d",state);
maybe try it with CGSShowCursor(CGSMainConnectionID())
This might also help : https://github.com/alexzielenski/Mousecape/blob/1d534b1e076b07a01b80364be23c88c8439028bc/Mousecape/mousecloak/NSCursor_Private.h
Warning. This code is not based on what is saved in preferences so combine it:
float cursorScale = 2;
cursorScale = MAX(1, MIN(cursorScale,4));
int connectionID = CGSMainConnectionID();
CGSSetCursorScale(connectionID, cursorScale);
to get the size
CGSGetCursorScale(connectionID, &cursorScale);
,
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *olddict = [defaults persistentDomainForName:#"com.apple.universalaccess"];
NSMutableDictionary *newdict = [olddict mutableCopy];
[newdict setObject:#4.0 forKey:#"mouseDriverCursorSize"];
[defaults setPersistentDomain:newdict forName:#"com.apple.universalaccess"];
[defaults synchronize];
NSLog(#"Cursor size set to %#", newdict);
CREDITS: Alex Zielenski

Switching between NSUserDefaults

Maybe somebody will know, how to "tag" NSuserdefaults.
I have like 10 user defaults:
name
pin
etc...
I want to make another object, which would have the same defaults as the other ones (same variables but different values). Like a version, if object == 1 then load one userdefaults and if object == 0 another ones. But how to make it done?
Tried to make something like this
[NSUserDefaults setVersion:object.intValue];
But i guess this isn't the way to do it. So maybe anyone could help how to make it done?
To be more specific a simple example would help like how to do this:
Object (1 or 0)
[[NSuserDefaults standartUserDefaults] setObject: #"something"
forKey: #"Name"];
[[NSUserDefaults standartUserDefaults] synchronize];
NSString *name = [[NSUserDefaults standartUserDefaults] stringForKey:"#name"];
How to set and get this Name depending on Object value?
You could use persistent domains.
NSMutableDictionary *defaults = [[[NSUserDefaults standardUserDefaults] persistantDomainForName:#"aString"] mutableCopy];
// make changes to the dictionary here
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:#"aString"];
[[NSUserDefaults standardUserDefaults] setPersistantDomain:defaults forName:#"aString"];
Note that you'll need to be accessing all the defaults via the dictionary if you do this.

Writing in NSUserDefault and .plist failed

I'm having trouble getting some informations written in my .plist, and NSUser Defaults Files.
For the .plist i've already posted but, nothing seems to be wrong:
How to update an array set into the .plist dictionary
And for the NSUser Default, i'm doing something like this:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *userSettings = [[NSMutableArray alloc]initWithArray:[[defaults objectForKey:#"userSettings"]mutableCopy]];
[userSettings addObject:userSettingsArray];//adding an array into my userSettingsArray (declared higher in the code)
[defaults setValue:userSettings forKey:#"userSettings"];
[defaults synchronize];
I was wandering, is there any option in Xcode allowing , or not, the user to write into the app folders?
I'm a little stuck here
Thank you for your help,
Tim
Use
[defaults setObject:userSettings forKey:#"userSettings"];
instead of
[defaults setValue:userSettings forKey:#"userSettings"];
I found the solution to my problem:
I entered the datas from the IBOutlet like this
[self.tempUserSettings insertObject:self.firstName atIndex:1];
instead of this:
[self.tempUserSettings insertObject:self.firstName.text atIndex:1];
so the datas were wrong, and so and so.....
Thank you for you help, learned a lot from those .plist and NSUserDefault

Check for first launch of my application

How would I check if it is the first launch of of my application using NSUserDefaults and running some code for the first time my app opens?
This should point you in the right direction:
static NSString* const hasRunAppOnceKey = #"hasRunAppOnceKey";
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:hasRunAppOnceKey] == NO)
{
// Some code you want to run on first use...
[defaults setBool:YES forKey:hasRunAppOnceKey];
}
The NSUserDefaults answer is the first thing that popped in my head, but upon reflection I will make another suggestion. A bit more work, but it's worth considering. The motive is: sometimes when troubleshooting an app, Apple recommends deleting that app's plist file. It's a fairly ubiquitous troubleshooting technique. I would recommend storing your boolean in your plist file instead of NSUserDefaults.
Disclaimer: I only do iOS development, so I'm not sure how NSUserDefaults and plists interact on the Mac, and I don't know what all is involved in getting your plist to live in ~/Library/Application\ Support/Preferences/com.mycompany.MyAppName.plist
Anyway, I imagine what this requires is having some code which can actually author a "fresh" plist (probably a copy from a template file in your bundle), and you app does this if it launches and does not see a plist. The default plist should not include the flag which lets your users skip the 'first time' code, but if they have opened the app before, and then delete the plist, they should get default behavior back.
This is an important behavior to support where possible, to aide our users if our app ever gives them trouble.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"hasBeenLaunched"]) {
// Run code on the first launch only ...
[defaults setBool:YES forKey:#"hasBeenLaunched"];
}
You can use NSUserDefaults to save bools, integers, objects into the program and have them available whenever you open it. You can use 'boolForKey' to set a flag called "hasBeenLaunched". By default, this value will be NO when not set. Once you change it to YES, the code in the if condition will never be executed again.
In your main controller class, implement something like this:
static NSString * const MDFirstRunKey = #"MDFirstRun";
#implementation MDAppController
+ (void)initialize {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:MDFirstRunKey];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
// the following if on Mac and is necessary:
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
BOOL firstRun = [[[NSUserDefaults standardUserDefaults]
objectForKey:MDFirstRunKey] boolValue];
if (firstRun) {
// do something
[[NSUserDefaults standardUserDefaults] setObject:
[NSNumber numberWithBool:NO] forKey:MDFirstRunKey];
} else {
// do something else
}
}
#end
The +initialize class method is called before an instance of the class it's found in is created; in other words, it is called very early on, and is a good place to set up your default values.
See Preferences and Settings Programming Guide: Registering Your App's Default Preferences for more info.

NSUserDefaults. setValue works, Not setBool

I trying to store some settings in NSUserDefaults, but It seems that the app won't store the setBool values.
This works:
[[NSUserDefaults standardUserDefaults] setValue: #"hello" forKey: #"test"];
[[NSUserDefaults standardUserDefaults] synchronize];
When I terminate the app and restart it, the value have been saved. However, when I do this:
[[NSUserDefaults standardUserDefaults] setBool: YES forKey: #"test"];
[[NSUserDefaults standardUserDefaults] synchronize];
It won't save after I close the app and restart it.
Should I file a bug report, or is there something I'm missing here?
Thanks
Edit:
I figure what I did wrong. In AppDelegate, I wanted to check if the boolForKey was set, and it it wasn't I did this:
if (![defaults boolForKey: #"test123"])
[defaults setBool: YES forKey: #"test123"];
... however, when it comes to boolWithKey, the "!" just check if the bool is YES or NO, not if its nil.
How can you be sure its not working? I tried your code and it works for me. Are you sure you are reading the Boolean in the correct way AFTER you write it?
This code SHOULD work:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:NO forKey:#"test"];
[defaults synchronize];
BOOL myBool = [defaults boolForKey:#"test"];
I had the same problem by having the following code in my AppDelegate to keep track of whether the user had seen a particular viewController to display a walkthrough, and then setting this Bool to NO after the user had seen it.
if (![standardUserDefaults boolForKey:#"firstViewOfVC"]) {
[standardUserDefaults setBool:YES forKey:#"firstViewOfVC"];
}
But then when you set it to NO later on and check if it "exists", you are actually seeing the NO boolean value and setting it back to yes. The quick fix is just to store the boolean value in an NSNumber object so that you can check for it's existence, independent of its value being YES or NO. See below:
if (![standardUserDefaults objectForKey:#"firstViewOfVC"]){
[standardUserDefaults setValue:[NSNumber numberWithBool:YES] forKey:#"firstViewOfVC"];
}
I had the exact same problem. Everything EXCEPT BOOLs were persisting correctly; but i was using some old coding styles from ios 3. recoded this way, everything works.
If anybody else is using old books.... here is an example
Bad stuff:
//////////// set / get bL2R
if (![[NSUserDefaults standardUserDefaults]
boolForKey:kL2RomanizationChoice]) {
[[NSUserDefaults standardUserDefaults]
setBool:YES
forKey:kL2RomanizationChoice];
bL2R = YES;
NSLog(#"L2Rom not found, set to YES.");
}
else {
bL2R = [[NSUserDefaults standardUserDefaults]
boolForKey:kL2RomanizationChoice];
NSLog(#"L2Rom found.");
if (bL2R) {
NSLog(#"L2Rom found to be YES.");
}
}
Good stuff:
if (![defaults boolForKey:kL2RomanizationChoice])
[defaults setBool:YES forKey:kL1RomanizationChoice];
L2String_Setting = [defaults objectForKey:kSecondLangChoice];
bL2R = [defaults boolForKey:kL2RomanizationChoice];
Update: sadly this only seemed to work briefly, and now is failing again... using Xcode 4.5.2. may just swap out bools for integers...
XCode 4.6 seems to have the same problem highlighted by hangzhouharry. A useful call is [[NSUserDefaults standardUserDefaults] dictionaryRepresentation] to see if your key values are looking the way they should.
For example -> autoLogout = 0;
which was set as a Bool, [settings boolForKey:#"autoLogout"] returns nothing
[settings integerForKey:#"autoLogout"] returns 0 (as, sort of, expected)