Saving data on the iPhone

Bryn Bellomy's picture

Over the past month or two, I've been fortunate enough to be able to put some work into an iPhone framework that interfaces with Drupal sites. In doing so, I've learned a number of things about the various Cocoa frameworks that might be useful to others. As such, I'm going to run a short series of blog posts documenting some of these discoveries.

First up is the subject of data storage -- particularly the storage of simple settings. There are several ways to accomplish this in the Cocoa touch framework. If your settings are fairly complicated and you don't expect your user to alter them very frequently, it's recommended that you hook into the Settings application (the way that Mail and Safari do, for example). Of course, this is cumbersome by virtue of the fact that you have to exit your app and start up Settings in order to change anything. If your user is tweaking settings frequently, this could spell a UI disaster.

Another option is simply to write settings to a file somewhere in your app's sandbox. For our Drupal/iPhone framework, I've adopted this approach. For most of our sites, I don't expect users to be dealing with very many settings -- username and password, of course, and perhaps a few other options tied into the Content Profile module. Therefore, while we can't take advantage of Apple's powerful Settings framework, data storage and retrieval is still very manageable.

One quick caveat that may seem silly at first. When it comes to interface design, do remember that with this approach you'll be using up valuable screen real estate with a "settings" button (and you can't begin to realize just how valuable this is until you start trying to assemble a complex UI). Plan for this well in advance or you'll find yourself redesigning your UI several times, perhaps even with completely different types of navigation controllers (which is a pain to redo). I began our app with a UINavigationBar, then switched to a UIToolbar, and finally recoded everything with a UITabBar when it became clear that the first two approaches were not appropriate. Not a very pleasant experience.

Objective C is still C, and you have all of the stdio functions at your disposal if you so desire. However, Cocoa comes with some great functions to assist you with file management and to keep you from writing to the wrong place in your app's sandbox. Here's a block of code to get us started. It saves a username and password to a binary file.

+ (BOOL) saveSettings:(NSString *)username password:(NSString *)password
{
        NSArray *dirArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                        NSUserDomainMask,
                        YES);
       
        NSString *path = [NSString stringWithFormat:@"%@/settings.bin", [dirArray objectAtIndex:0]];
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:username, @"username", password, @"password", nil];     
        NSData *data = [NSPropertyListSerialization dataFromPropertyList:dict format:NSPropertyListBinaryFormat_v1_0 errorDescription:nil];
       
        if (data == nil) {
                NSLog(@"Error in SettingsDataController saveSettings");
                return NO;
        }
       
        NSLog(@"Writing to path: %@", path);
       
        if ([data writeToFile:path options:NSAtomicWrite error:nil] == NO) {
                NSLog(@"writeToFile error");
                return NO;
        }
       
        NSLog(@"writeToFile success");
        return YES;
}

Let's step through the first part of this save function. NSSearchPathForDirectoriesInDomains() returns an array of directories that match the search criteria. In this case, we're asking it to give us the documents directory for the app within the user's domain (rather than the systemwide domain). This is basically where you'll want to store most of your app's data, settings or otherwise. The second line of the function creates the full path to the settings file. In the third line, we're creating an NSDictionary object with the settings (be sure to end your dictionary with nil! Common mistake). The fourth line serializes the NSDictionary into an NSData object. If there are errors, the NSData object will be nil. If you skip a few lines down, you'll notice that the NSData class also provides methods for writing itself into a file. The NSAtomicWrite setting writes the data to a backup file first and then copies it to the specified path, preventing write errors from corrupting your settings. It's highly recommended that you write atomically.

Next time around I'll have some advice on caching settings to prevent unnecessary writes. Future articles will address sharing data between UITextViews and other ViewControllers, .xib files, using Interface Builder vs. building interfaces programmatically, and Drupal XML-RPC. Stick around!