NSUserDefaults not saving properly - iphone

I have issues with NSUserDefaults and I don't quite understand what is going on.
My App has 5 levels and each level does the exact same thing with NSUserDefaults (Retrieves the levels defaults, changes the value as the user plays the level and then sets the defaults and syncronizes at the end of the level) the first 4 levels. Work without a hitch but the last level doesn't save the values. The app doesn't crash and the last level isn't the very last thing that happens, And I even have the defaults synchronized when the application terminates. Is there a max size on the NSUserDefaults or is there anything anyone can think of that I haven't, I'll post the code below but like I said the first four levels work perfectly
//header
NSUserDefaults *userData;
#property(nonatomic,retain) NSUserDefaults *userData;
//class file
//Sets the boolean variables for the class to use
userData = [NSUserDefaults standardUserDefaults];
boolOne = [userData boolForKey:#"LevelFiveBoolOne"];
boolTwo = [userData boolForKey:#"LevelFiveBoolTwo"];
boolThree = [userData boolForKey:#"LevelFiveBoolThree"];
boolFour = [userData boolForKey:#"LevelFiveBoolFour"];
boolFive = [userData boolForKey:#"LevelFiveBoolFive"];
boolSix = [userData boolForKey:#"LevelFiveBoolSix"];
boolSeven = [userData boolForKey:#"LevelFiveBoolSeven"];
//End Of Level
[userData setBool:boolOne forKey:#"LevelFiveBoolOne"];
[userData setBool:boolTwo forKey:#"LevelFiveBoolTwo"];
[userData setBool:boolThree forKey:#"LevelFiveBoolThree"];
[userData setBool:boolFour forKey:#"LevelFiveBoolFour"];
[userData setBool:boolFive forKey:#"LevelFiveBoolFive"];
[userData setBool:boolSix forKey:#"LevelFiveBoolSix"];
[userData setBool:boolSeven forKey:#"LevelFiveBoolSeven"];
[userData synchronize];
When when I switch to the view that uses these defaults they values are correct but when I terminate the application and restart it, these values aren't saved, every other level does the exact same process this is the only level that doesn't work.
I've stared at this for quite awhile and I'm hoping someone out there has run into the same problem and can give me some insight on how they resolved it.

NSUserDefaults might not have a chance to save depending on how the process is terminated.
This answer has more info: Why is NSUserDefaults not saving my values?

Just in case someone runs accross this: When storing an NSDictionary or NSArray (or mutable Objects of both of them) in the user defaults and they have an NSURL Object stored, it won't save the data on synchonize!

Somewhere you have something like:
// load the default values for the user defaults
userDefaultsValuesPath=[[NSBundle mainBundle] pathForResource:#"UserDefaults" ofType:#"plist"];
userDefaultsValuesDict=[NSDictionary dictionaryWithContentsOfFile:userDefaultsValuesPath];
// set them in the standard user defaults
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict];
If the initial defaults you are setting up do not have LevelFive defaults, then the calls would fail.
Check the return value from -synchronize for errors.

I decided to put this issue aside and continue development which included adding things after level five so the user can loop through levels and return to the main menu and so on and so forth...and I'm not sure why but the userDefaults are saving for level five now so I don't know if it's because before level five was the very last thing the application did and even though it didn't terminate itself and did other things maybe it wasn't actually writing the defaults to disk...I'm still not sure what was wrong but it's working now and I can't get it to fail to see if I can get an error with the synchronize...
Thanks for the help

Related

Appending Value to NSMutableArray and Saving to NSUserDefaults

I am creating a names generator app for iPhone and this is what I am trying to do.
Allow the user to save the current generated name by clicking a save button.
In order to do this, here is what is happening:
The current babyname is displayed in a UILabel.
The user presses 'save' and then the label.text value is appended to a NSMutableArray (I don't think this is working in my code correctly).
The NSMutableArray is then saved in NSUserDefaults.
The contents will then be loaded in another view to populate UITableView cells.
My specific question is, am I handling this saving/persistent storage process correctly? Here is the snippet in question:
- (IBAction)saveName:(id)sender {
// save current baby name to array
NSMutableArray *babyNameArray = [[NSMutableArray alloc] init];
[babyNameArray addObject:babyname.text];
// save list of babynames to nsuserdefaults
[[NSUserDefaults standardUserDefaults] setObject:babyNameArray forKey:#"My Key"];
// for testing log names listed in nsuserdefaults
NSLog(#"%#", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
}
Here is a link to the pastebin of the whole file contents (with comments): http://pastebin.com/hQRM9Azh
Every time you add, you're starting again with an empty array ([[NSMutableArray alloc] init]) – instead of starting with the existing items.
Here's some code which adds to the existing entries instead:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *babyNameArray = [[defaults stringArrayForKey:#"My Key"] mutableCopy];
if (!babyNameArray) babyNameArray = [[NSMutableArray alloc] init];
[babyNameArray addObject:babyname.text];
[defaults setObject:babyNameArray forKey:#"My Key"];
[defaults synchronize];
A couple of things before we start addressing the big issue. First, you'll want to have those arrays of names stored somewhere in a file(s), which you'll read either when the application starts, or in ViewWillAppear/ViewWillLoad.
Second, the way you have things working right now, you will only save one name at a time. You alloc & init a new (empty) array every time the user clicks the "Save Name" button. You then add the current name to this (empty) array and set it as the object for key "My Key." Note that your "My Key" object will always have only one element - the most recently saved name.
And lastly, you never actually save your changes to NSUserDefaults. If I recall correctly, after you are done making changes to it, you need to call synchronize - otherwise your changes will be lost as soon as the application closes. Which kind of kills the whole purpose of using data persistance. :)

How to call a specific user default in iOS

I'm trying to call up a specific setting that is saved in the settings bundle of my iphone app. I want to do an if statement based on what was saved. My code in the implementation file looks like this:
branchMatch = [[NSString alloc] initWithString:[defaults objectForKey:#"branch"]];
The object in the settings file is just the name of the specific branch. I keep getting the SIGABRT error but I'm not sure why.
First, there's no need to initialize a string with another string. Assuming that branchMatch is a NSString*, the following would suffice:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
branchMatch = (NSString*)[defaults objectForKey:#"branch"];
Second, check if objectForKey returns a nil. Your SIGABRT is, most likely, due to a nil there.
EDIT: those values aren't present in the collection until the user opens up Settings and explicitly changes them. It's up to you to provide the sensible value if the setting is not found (is nil). The ones in the settings bundle are not automatically applied; they are only for the Settings app to initialize its UI properly.
Are you checking that the objectForKey call isn't returning nil?
Do this instead :
NSUserDefault* defaults = [NSUserDefauls standardUserDefaults];
NSString* setting = [defaults objectForKey:#"branch"];
if (setting)
// Do what needed if branch is set
else
// Do what needed if branch has never been set
After much hair pulling, I realized that I need to load user defaults from the delegate class instead of the ViewController.
Thanks for all the input

Unexpected results from NSUserDefaults boolForKey

After uninstalling an application completely from the device and then loading it in the debugger, I am attempting in a setup method to load a flag using boolForKey. The first time the app runs I have the expectation that the bool will not exist, since I have just reinstalled the app. I expect from the documentation that boolForKey will therefore return NO.
I am seeing the opposite though. boolForKey is returning YES, which fubars my initial user settings. Any idea why this might be happening or a good way around it?
BOOL stopAutoLogin = [[NSUserDefaults standardUserDefaults] boolForKey:#"StopAutoLogin"];
_userWantsAutoLogin = !stopAutoLogin;
So stopAutoLogin comes out as "YES", which is completely unexpected.
Stranger and stranger: When I call objectForKey:#"StopAutoLogin" I get a nil object, as expected. It's just the boolForKey that returns a bad value. So I changed the code to this:
// this is nil
NSObject *wrapper = [[NSUserDefaults standardUserDefaults] objectForKey:#"StopAutoLogin"];
// this is YES
BOOL stopAutoLogin = [[NSUserDefaults standardUserDefaults] boolForKey:#"StopAutoLogin"];
please try [UserDefaults synchronize];
Because this method is automatically invoked at periodic intervals, use this method only if you cannot wait for the automatic synchronization (for example, if your application is about to exit) or if you want to update the user defaults to what is on disk even though you have not made any changes.
please see: http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
Do you register the default values for your keys?
NSMutableDictionary *appDefaults = [NSMutableDictionary dictionaryWithCapacity:1];
[appDefaults setObject:#"NO" forKey:kReloadOnStartKey];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults registerDefaults:appDefaults];
If there is no registration domain,
one is created using the specified
dictionary, and NSRegistrationDomain
is added to the end of the search
list.
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.
See this link for more information.

Is it possible to read the structure of plist file?

I know that NSUserDefaults can read the key and value from plist file.
Is it possible to read the structure of a key?
For example:
Key 'Count' is an integer and has option
1,2,3,4,5
the codes below can get the value 'Count'
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
if([defaults objectForKey:#"Count"]!=nil)
{
NSString *s=[[NSString alloc] initWithString: [defaults objectForKey:#"Count"]];
NSInteger v=[s intValue];
[s release];
}
But I prefer to get all options and store to a NSArray or a better storage structure.
Is it Possible?
I read bundle settings the following way:
// Get path to Root.plist file in settings bundle and retrieving its contents
NSString* tPath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
settingsBundle = [[NSBundle bundleWithPath:tPath] retain];
NSDictionary* tSetDict = [NSDictionary dictionaryWithContentsOfFile:[tPath stringByAppendingPathComponent:#"Root.plist"]];
// Get array of preference dictionaries
NSArray* prefs = [tSetDict objectForKey:#"PreferenceSpecifiers"];
// Iterate through dictionaries to find required value
for (NSDictionary* setDict in prefs){
NSString* type = [setDict objectForKey:#"Type"];
if (![type isEqualToString:#"PSMultiValueSpecifier"]){
// Get possible preference values for PSMultiValueSpecifier case
// You may need to know value type in advance - not sure about that
NSArray* values = [setDict objectForKey:#"Values"];
}
}
I'm not exactly sure I understand your question but you can't directly access the user defaults system save through its defined methods. You can't read them out in one big chunk.
The defaults system isn't actually a means of reading plist files, its an more of an API for accessing a database maintained by the OS itself. Although, you don't see it much on the iPhone, its actually a very large and complex system for managing preferences not only for individual apps but also for users, groups of users, computers and networks. It seems trivial on iOS because you don't have the flexibility of configuration and use that you have on MacOS proper.
It would be impossible to read out the entire defaults because they are huge and much larger than you would expect even on iOS.
Instead of jumping through the hoops in the code you showed in the answer, you should access the data in the key using one of the dedicated methods for the type of data stored. In this case:
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSInteger v=[defaults integerForKey:#"Count"];
Even if you did read the defaults out in one chunk, you would just find yourself using the same type of calls and code to access the data in the alternate data structure as you would in using the defaults in the first place. You might as well use the defaults system.

Saving application data state on exit

I have an NSMutableArray with 24 strings.
I need to save this data if a user gets a call or quits the application.
I have been looking into many examples but for some reason can’t seem to determine the best way to save the data.
The 24 strings correspond to 24 buttons and their state. When a button is clicked, it displays the corresponding Array info for that buttons tag (0 – 23). What I would need to retain is if 10 buttons where clicked and their data shown, how/what would be the best way of retaining this data so it can be reloaded when the app starts?
I am thinking I would need to store:
Button Tag,
Buttons corresponding Array value,
Button state (whether it has clicked and value is show or not)
I would store this data on exit of the application and then when app is started again, I would determine if this stored data exists, if so populate the array and examine the button states to determine if it had already been shown and if so, set it accordingly. Then when this file was loaded, I would delete the stored data (.DAT file if stored this way). This way if a user quits gracefully, on next start up, it would start a new game.
I have looked at several examples where they store data into a .DAT file but am having problem implementing this….and wondering if this is even the best way.
Any help or thoughts on this is greatly appreciated.
Geo…
you should be able to store it in NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:buttonState forKey:#"buttonState"];
[defaults synchronize];
// Then to get the state back
NSMutableArray* buttonState = [[[NSUserDefaults standardUserDefaults] arrayForKey:#"buttonState"] mutableCopy];
You could save the data to a plist in the Documents directory. If the data is there, load it up, and if not, it would suggest a clean run.
To load the data:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documents = [paths objectAtIndex:0];
NSString *filePath = [documents stringByAppendingPathComponent:#"buttonState.plist"];
NSMutableDictionary *buttonState = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
To save the data:
[buttonState writeToFile:filePath atomically: YES];
I save my data by using NSKeyedArchiver/NSKeyedUnarchiver, since your NSMutableArray already supports the NSCoding protocol, you can just use
yourArray = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
[NSKeyedArchiver archiveRootObject:yourArray toFile:file];
Edit:
I suppose NSArray's own methods work also
[NSArray writeToFile:]
[NSArray initWithContentsOfFile:]