NSUserDefaults not saving - ios

I am having a problem in my sprite kit app where my NSUserDefaults variable is not working. In createSceneContents (which I know is being called)
if (![defaults objectForKey:#"obj"]) {
difficultyLabel.text = #"Difficulty: Easy";
[defaults setObject:#"Easy" forKey:#"obj"];
} else {
difficultyLabel.text = [NSString stringWithFormat:#"Difficulty: %#", [defaults objectForKey:#"diff"]];
}
and then when you click on the SKLabelNode to change the difficulty and this code is being called
if ([label.text isEqualToString:#"Difficulty: Easy"]) {
label.text = #"Difficulty: Hard";
[defaults setObject:#"Hard" forKey:#"obj"];
NSLog(#"%#",[defaults objectForKey:#"obj"]);
} else {
label.text = #"Difficulty: Easy";
[defaults setObject:#"Easy" forKey:#"obj"];
NSLog(#"%#",[defaults objectForKey:#"obj"]);
but when I stop the program and run it again, it always just says Difficulty: Easy. Any suggestions?

As rmaddy's answer explains, NSUserDefaults won't immediately write data to long-term storage.
The values are saved in temporary memory when you call setObject:forKey:, so if you ever try to call objectForKey: after setObject:forKey:, it will return the value in memory if it exists, and if not, it goes to look for what's been saved to long-term storage.
In the background, the device will eventually save these values to permanent storage. It's something the OS handles, but in normal operation of your app, it shouldn't be necessary to immediately store these values to permanent storage most of the time, so let the OS do this at times when its been optimized to do so (I imagine the OS probably synchs every app's user defaults at once on some regular schedule, and as much as possible, tries to do this when the processor is idle probably).
But with that said, as rmaddy explains, if you've got something that crucially needs to be saved to the permanent storage immediately, you can always call synchronize. Keep in mind though... as long as your app isn't killed while in debug mode, the values you've set to be stored in user defaults will be stored.
But for your own sake, if you want to be absolutely certain, you can put it a call to synchronize in applicationDidEnterBackground as rmaddy suggests. Keep in mind though, this method isn't called if you kill the app.

When you say you "stop the program", what do you mean? Are you running in the debugger and killing the app and rerunning it again? NSUserDefaults doesn't persist your changes immediately. Either call [defaults synchronize] after writing important data or at least add such a call to your app delegate's applicationDidEnterBackground method. And then be sure you put your app in the background before killing it to ensure the data is written first.

To store:
NSString * str = #"name123";
NSUserDefaults *def=[NSUserDefaults standardUserDefaults];
[def setObject:str forKey:#"Key"];
[def synchronize];
NSLog(#"str = = %#",str);
To Retrieve:
NSUserDefaults *def=[NSUserDefaults standardUserDefaults];
NSString *str2 = [def valueForKey:#"Key"];
NSLog(#" Saved str = = %#",str2);

I found another reason why NSUserDefaults not saving,following code can make value in NSUserDefaults not saving:
NSUserDefaults *def= [[NSUserDefaults standardUserDefaults] init];
...
NSUserDefaults *def= [[NSUserDefaults standardUserDefaults] initWithSuiteName:#"123"];
You can search NSUserDefaults in your project to catch those init twice bug.

I had this issue where it was working previously but then wasn't. I couldn't figure it out.
It turned out restarting my mac fixed it. There must be some issue with the prefsd daemon that runs in the background that can cause this I'm guessing

Related

How to check if current version of the app has launched before?

I'd like to show a user agreement / disclaimer the first time my iOS app is launched. Currently, I'm simply doing it with the following code in didFinishLaunchingWithOptions in the AppDelegate:
//Show disclaimer to user, if he never agreed to it yet
if(![[NSUserDefaults standardUserDefaults] boolForKey:kHasAgreedToDisclaimerKey])
[self showDisclaimer];
However, I need to show a new disclaimer every time a new version of the app is installed and launch for the first time.
One way I was thinking of solving this problem was by creating a different UserDefaults key for each app version, but that looks like it will leave a lot of junk keys on the user's device.
Is there a better way to solve this?
Close. Don't save something for each app version, just store the last opened app version and compare it to the current app version. Something like this:
//Get the last opened and current versions
NSString *lastOpenedV = [[NSUserDefaults standardUserDefaults] objectForKey:#"LastOpenedVersion"];
NSString *currentVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"];
//Show an alert if necessary
if (lastOpenedV == nil || ![lastOpenedV isEqualToString:currentVersion]) {
[self showDisclaimer];
//Update the last opened version
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:#"LastOpenedVersion"];
}
Sounds like you have the right idea.
I'd try something like this:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *storedVersion = [prefs objectForKey:#"AppVersion"];
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSString *bundleVersion = [infoDict objectForKey:#"CFBundleVersion"];
if (storedVersion) {
if (![storedVersion isEqualToString:bundleVersion]) {
[prefs setObject:bundleVersion forKey:#"AppVersion"];
[prefs synchronize];
// Show disclaimer
}
} else {
[prefs setObject:bundleVersion forKey:#"AppVersion"];
[prefs synchronize];
}
Basically, you are going to copy the bundle version to userDefaults, and compare it on every launch. If it isn't present in defaults, then you know the app was just installed, and you want to display the disclaimer. If it is in the defaults, then you want to check it against the bundle version. Anytime the bundle version doesn't match, you know the app has been updated.
Edit: Looks like took too long to mock up my example. I'm ten minutes too late, and you've found an answer. But I'll leave it here anyways :).
NSUserdefaults will be deleted when the app gets deleted, though data will persist on update. If that is OK for you, you could save a version number of the accepted version there, as mentioned by #Nick in the comment.
To make your information really persistant, write a version key to the keychain when the user accepted.

NSUserDefaults not being saved in simulator ,even though I use synchronize

I add a dictionary to NSUserDefaults and call 'synchronize' method,but when I close my app and reopen it the values which are saved in NSUserDefault are nil.
#define kStartTime #"startTime"
#define kEndTime #"endTime"
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSString *startTime=#"08:00";
NSString *endTime=#"18:00";
NSDictionary *dict=[[NSDictionary alloc] initWithObjectsAndKeys:startTime,kStartTime,
endTime,kEndTime,
nil];
[defaults registerDefaults:dict];
[defaults synchronize];
And in the viewDidLoad method I call this:
NSString *startTimeValue=[defaults stringForKey:kStartTime];
the startTimeValue is still nil When I reopen my app in simulator.Is UserDefaults can't save in Simulator???
My point is when i close my app and reopen it in simulator the values are gone.
After I searched the apple document ,I got this.
registerDefaults:.
As is said in Apple Document :Adds the contents of the specified dictionary to the registration domain.
*The contents of the registration domain are not written to disk;*you need to call this method each time your application starts.You can place a plist file in the application's Resources directory and call registerDefaults: with the contents that you read in from that file.
So the function of the registerDefaults: is setting Default values in app,The Default value will not stored in disk by synchronize
I think you try to read the value from NSUserDefaults before you have registered the defaults.
If you register defaults these values are not saved to disk, they will not persist to the next application launch. You have to register the default values every time you launch the app.
When you read an object from NSUserDefaults and you have not saved a value before, the registered default value will be returned. If you have saved a value before, this value will be returned.
In the code you posted you have not saved anything to userDefaults. So the value you have registered as default will be returned. If you haven't registered any values this will return nil.
The code that registers the defaults should be put in a method that runs very early in the application life cycle. I would recommend to use application:didFinishLaunchingWithOptions:
If you want to save multiple values to NSUserDefaults you have to use multiple setObject:forKey: calls.
registerDefaults: can not be used to save multiple keys to NSUserDefaults at the same time!
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSDictionary *dict=[[NSDictionary alloc] initWithObjectsAndKeys:startTime,#"kStartTime",
endTime,#"kEndTime",
nil];
[defaults registerDefaults:dict];
[defaults synchronize];
NSString *startTimeValue=[defaults stringForKey:#"kStartTime"];
NSLog(#"%#",startTimeValue);
you need to add keys like #"value" like this.
Now try this code....

Setting initial NSUserDefaults

I have seen some guides that say to do this, and others that say setting initial values and keys of NSUserDefaults this way will cause problems when an app is updated. I'm hoping I can definitively answer that here. In my appdelegate didFinishLaunchingWithOptions method I have:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults integerForKey:#"totalDays"]) {
// if there is no value for the key, set it to 1
[defaults setInteger:0 forKey:#"totalDays"];
}
if (![defaults objectForKey:#"currentDate"]) {
[defaults setObject:#"32 01" forKey:#"currentDate"];
}
if (! [defaults boolForKey:#"marked"]) {
[defaults setBool:NO forKey:#"marked"];
}
if (![defaults arrayForKey:#"checkedrows"]) {
NSMutableArray *arr1 = [NSMutableArray arrayWithArray:[defaults arrayForKey:#"checkedrows"]];
}
[defaults synchronize];
It might also be helpful to note, you don't "need" default values in there to use them. It might save you time from having to check them for null or zero values later in the app.
As the other answer stated, once values are saved to user defaults, it should persist from update to update and backup to backup. I've been using them for years with no issues with lost or corrupted data.
The data stored in the user defaults are save if the user updates the app. You will not get any problems with your code after an update.
You should not set the default values at all! Instead, use the handy method NSUserDefaults provides for exactly this purpose: -registerDefaults:. Call it early in your program's startup with a dictionary of the default values.
The advantage of this is that because -registerDefaults: is transient, you can tell the difference between "the user hasn't customized this preference" and "the user explicitly set this preference to the default value", which is important if you change default settings in a newer version of the program. It's also a little faster due to not needing to save to the disk.

is that possible to know my apps is first time run or not in xcode?

i create an apps that i want to prompt an message for the user.. maybe using UIAlertview. After that, if the user run the apps for the second time, the alert won't prompt up anymore.
is that possible? honestly i don't have any idea about how to doing this.
Any idea? I search on STO, actually this link, still confused.
what is NSUserDefaults? How can NSUserDefaults store this information? i mean this is my first time or second time.
thanks.
To know what's NSUserDefaults, I suggest to take a look the official doc.
And of course you can use it to fulfill your goal.
You use a user default to store information about the current amount of runs in the app.
More or less like:
BOOL isRunMoreThanOnce = [[NSUserDefaults standardUserDefaults] boolForKey:#"isRunMoreThanOnce"];
if(!isRunMoreThanOnce){
// Show the alert view
// Then set the first run flag
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"isRunMoreThanOnce"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Yes, U can save a value in NSUserDefault for the first time in your app & set it some other value once you open the app.
like
if(![[NSUserDefault standardUserDefault] objectforKey:#"AppOpenFirstTime"])
{
// App Open First time
// Show Alert
[[NSUserDefault standardUserDefault] setObject:#"1" forKey:#"AppOpenFirstTime"]
}
You can check if you stored some value in NSUserDefaults
NSString *flag = [[NSUserDefaults standardUserDefaults] stringForKey:#"not_first_run"];
if (!flag) {
//first run, do somethig
}
and then set it to some value
[[NSUserDefaults standardUserDefaults] setObject:#"just any string" forKey:#"not_first_run"];
NSUserDefaults is a key-value store saved between your application launches.
You can do it exactly as cortez said in your link. NSUserDefaults is written to disc, and will be created and accessed from your app.
See this link
First Time when your application launch at that time boolForKey:#"AlreadyRan" is FALSE. after that set it TRUE.
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"AlreadyRan"] )
{
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"AlreadyRan"];
}
With the NSUserDefaults class, you can save settings and properties
related to application or user data.
The objects will be saved in what is known as the iOS “defaults system”.
The iOS defaults system is available throughout all of the code in your app, and any data saved to the defaults system will persist through application sessions.This means that even if the user closes your application or reboots their phone, the saved data will still be available the next time they open the app!

How to determine that user runs the app for the first time?

I got a problem of my iOS app recently. In my app, an instruction view will appear at the first time of running, then hide from then on. How can I implement this effect?
Try to use this function:
- (BOOL) isFirstRun
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"isFirstRun"])
{
return NO;
}
[defaults setObject:[NSDate date] forKey:#"isFirstRun"];
[[NSUserDefaults standardUserDefaults] synchronize];
return YES;
}
In your app delegate check for a key in the user defaults (your own custom key, something like "AppWasAlreadyStartedPreviously"). If the key doesn't exist yet, it's the first run. You can show your instruction view and add the key to the user defaults. The next time the user starts the app you'll find the key in the user defaults and know that it's not the first run.
See the documentation of NSUserDefaults.
Store a file and check if the file exsists every time when you start the app. If tr file does not exsists then show intro, and then create the file.
The only way I think is to store a value into a specified file, when run the app, you should check the value first, and then you can deal with the result whether the app has already been run.

Resources