Figuring out how to store data persistently in my iOS app took me all damn day but I did it! Between multiple example implementations of saving iOS app data, I finally managed to piece together a comprehensive example template you can use in Xcode. This implementation, in my opinion, is a little bit more clear and intuitive though all credit goes to my references for the starting point.
The sample implementations I am talking about are here, here, and here, saved for posterity and my own future reference in case things go FUBAR with my implementation.
Where to begin… let’s start with a general synopsis: this implementation uses only one of many ways to save your application’s data. It’s one of the simpler methods and utilizes something called Property Lists, a standard in Apple programming. It’s good for storing anything basic to moderate and can even store data types such as NSArray and NSDictionary. Since between those two you can store nearly anything you could need, I believe this to be a quick and easy solution.
First of all, you’ll need to create a base plist file which your app can use to load its settings on its very first run. To create it, you need to open up the Xcode interface. Insert a new file, under the iOS section select “Resource” and then click on the “Property List” icon on the right. Then you will be prompted to give it a name, I gave it “GameSave” so the file created was “GameSave.plist”.
Now you will see it in your main project directory, but you will have to drag it over to the “Supporting Files” folder to have access to it. It still needs a bit of modification to be ready. Create a new entry in the file and make sure its type is “Dictionary.” You can give it any name you want.
Expand your new dictionary type and add a new row, this of Number type (this is what is used in my example, you may want to use something else here). Also be sure to set the value. Give it the key name of “CurrentGameState” which is what I will be using. It is a number that is used to indicate to the app what state it is in. What is for is not so important.
Once this is done, you have a plist that looks like this:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CurrentGameState</key>
<integer>0</integer>
</dict>
</plist>
The rest of the steps will be done purely in the code implementation.
The idea here is to use that as a base file which you can then copy to your app’s Documents directory where it will be readable and writable. This implementation has checks for whether the copied file exists in your app and if it doesn’t, will copy it over. After that, the app will simply read from the file, whether it previously existed or was just copied. It doesn’t matter at that point.
The first thing to do is define a function that will return the path to your app’s plist file in the Documents directory in your main view controller. Then you will define functions for saving and loading the app date. Finally, you will add your own handler for applicationDidEnterBackground in your controller.
This should go in the .h file:
@interface GameViewController : UIViewController
- (NSString*)saveGameDataPath;
- (void)loadGameData;
- (void)saveGameData;
- (void)applicationDidEnterBackground:(UIApplication *)application;
@end
The implementations belong in the .m file. Let’s start with the first function that we declared.
- (NSString *) saveGameDataPath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[path objectAtIndex:0] stringByAppendingPathComponent:@"GameSave.plist"];
}
saveGameDataPath Notes: This looks for a copy of the GameSave.plist file we created earlier in our Xcode project as a base file in the Documents directory. Each app gets its own Documents folder in iOS to store its application data.
- (void)loadGameData
{
NSString *dataPath = [self saveGameDataPath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:dataPath];
if (!fileExists) { // a save file does not exist, create it for the first time
// grab the master game save data that is shipped with the game
NSString *bundleFile = [[NSBundle mainBundle]pathForResource:@"GameSave" ofType:@"plist"];
//copy the file from the bundle to the game's own Documents directory
[[NSFileManager defaultManager]copyItemAtPath:bundleFile toPath:dataPath error:nil];
}
// ok to read values from the plist (whether already existing or just copied from master)
NSDictionary *dict = [[NSMutableDictionary alloc] initWithContentsOfFile:dataPath];
currentGameState = [[dict valueForKey:@"CurrentGameState"] integerValue];
}
loadGameData Notes: It uses the path function we created earlier to look up whether it exists or not. If it doesn’t, it copies over the one supplied with the project to the app’s Documents directory. After this is done, the GameSave.plist can now be read. Either it was freshly copied over or else had previously been created. Regardless, the application data is now present. An NSDictionary class is used to read in the .plist data (which for consistency is also used when saving to the file below). It’s just an empty core implementation at this point, which allows for a object to be retrieved with a key. You could have used an NSArray for the same effect but you would need to save data as an array for consistency. Just remember this when you are using your own data structures.
- (void)saveGameData
{
NSString *dataPath = [self saveGameDataPath];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:dataPath];
//adding the new objects to the plist
NSNumber *stateNumber = [[NSNumber alloc] initWithInt:currentGameState];
[dict setObject:stateNumber forKey:@"CurrentGameState"];
//finally saving the changes made to the file
[dict writeToFile:dataPath atomically:YES];
}
saveGameData Notes: This function grabs the path to the GameSave.plist file and writes to it.
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self saveGameData];
}
applicationDidEnterBackground Notes: This is your opportunity to call saveGameData. This function gets automatically called whenever the app enters background mode. This happens when the user pressed the Home button. Whether they make it back to your app or not you can’t take any chances. You will need to save the data here in case they don’t come back and your app gets kicked out of the running threads by the iOS thread manager (this was Apple’s new multithreading feature introduced in 5.0).
Now, your program won’t automatically call the controller’s applicationDidEnterBackground function unless you register it explicitly. This is done in the viewDidLoad function:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIApplication *myApp = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:myApp];
[self loadGameData];
}
Note that I also added a call to loadGameData here. This makes sure the saved data gets loaded when the app starts.
That’s all there is to it. Hopefully you found this clearer than the source examples I based this implementation off of. If there are any questions or clarifications needed please let me know in the comments.