- Working with Uniform Type Identifiers
- Accessing the System Pasteboard
- Recipe: Passively Updating the Pasteboard
- Recipe: Enabling Document File Sharing
- Recipe: Monitoring the Documents Folder
- The Document Interaction Controller
- Recipe: Checking for the Open Menu
- Declaring Document Support
- Recipe: Implementing Document Support
- Exported Type Declarations
- Creating URL-Based Services
- Recipe: Adding Custom Settings Bundles-Or Not
- Summary
Recipe: Adding Custom Settings Bundles—Or Not
iOS uses the NSUserDefaults class to access and manage application preferences. With it, you can store information that your application needs to preserve between successive runs. For example, you might save a current player name, a list of high scores, or the last-used view configuration. User defaults programmatically assign values to a persistent database associated with your application. These defaults are stored in your application sandbox’s Library folder, in a property list file named with your application identifier.
Treat user defaults as a mutable dictionary. Set and retrieve objects using keys, just as you would with that dictionary. Defaults entries are limited to standard property list types—that is, NSString, NSNumber, NSDate, NSData, NSArray, and NSDictionary. When you need to store information that does not fall into one of these classes, consider using another file (such as one that resides in your sandbox’s Library folder) or serialize your object into NSData and store that data in defaults.
The synchronize method forces the defaults database to update to the latest changes made in memory. Synchronizing assures you that the file-based defaults data is up to date, an important factor if your application gets interrupted for some reason. The following snippet demonstrates setting, synchronizing, and retrieving data from the user defaults system:
[[NSUserDefaults standardUserDefaults] setObject:colors forKey:@"colors"]; [[NSUserDefaults standardUserDefaults] setObject:locs forKey:@"locs"]; [[NSUserDefaults standardUserDefaults] synchronize]; NSLog(@"%@", [[NSUserDefaults] objectForKey@"lastViewTag"]);
The Settings App
iPhone applications can add custom preferences into the main Settings app (see Figure 16-9). These preferences access the same application-specific user defaults that you work with programmatically. Any changes your users make to these screens update and synchronize with standard user defaults. The difference is that Settings provides a “friendly” GUI for your users and that the chances of your users stumbling across these settings is, practically zero.
A word of caution: Avoid using Settings bundles with your apps. Build your preferences directly into your app or prepare for an onslaught of customer service requests because the discoverability of this feature is marginal. I include coverage of this topic out of a sense of courtesy. Although they are not easily discoverable for users, they are particularly helpful for kiosk-style apps. When working with apps intended for trade shows or other public use, using Settings bundles helps hide those defaults items you don’t want to expose in-app.
Custom settings are listed after system settings, but otherwise look and act like the ones that Apple preloaded into your system. As the screenshots in Figure 16-9 show, custom preferences provide a variety of data interaction styles, including text fields, switches, and sliders.
Figure 16-9. (Left) Custom settings bundles for third-party applications appear on the Settings screen after the built-in settings. On the iPhone, you may have to scroll down a bit to find them. (Right) Developer-defined preferences elements can include text fields (both regular and secure), switches, sliders, multivalue choices, group titles, and child panes.
Because these settings create standard NSUserDefaults entries, you can easily query and modify any of these settings from code. For example, Recipe 16-7 defines a field called “Name” (see Figure 16-9, right screenshot, first item in the top Group). This text field stores its value to the @"name_preference" key. You can see whether the user has entered a value for this key from your application.
NSLog(@"% objectForKey:@"name_preference"];
Avoid Sensitive Information
Use settings to store nonsensitive account preferences such as usernames and option toggles. Although passwords are visually obscured with dots, they’re stored in clear text in your application sandbox. When working with sensitive information, always use your iPhone’s secure keychain instead. Settings bundles do not offer keychain integration at this time. (Keychain recipes appear in Chapter 15.)
Settings Schema
A copy of the settings schema resides in your Developer folder at /Developer/Platforms/ iPhoneOS.platform/Developer/Library/Xcode/Plug-ins/iPhoneSettingsPlistStructDefs.xcodeplugin. Xcode uses this file to check its property list syntax. In the file, you can see all the definitions and the required and optional attributes used to specify custom preferences. If Apple should ever expand or change its definitions, you’ll be able to find those changes in this file.
Defining a Settings Bundle
Each settings pane corresponds to one property list file. Recipe 16-7 shows the source for the pane in Figure 16-9 (right). It demonstrates each SDK settings type and provides a sample definition. Types include text fields (strings), sliders (floating-point numbers), switches (Boolean values), and multiple selection (one-of-n choices). In addition, you can group items and link to child panes.
To add new settings, build a dictionary and add it to the PreferencesSpecifiers array. Each individual preference dictionary needs, at a minimum, a type and a title. Some settings, like the PSGroupSpecifier group item, require nothing more to work. Others, such as text fields, use quite a few properties. You want to specify capitalization and autocorrection behaviors as well as the keyboard type and whether the password security feature obscures text, as you can see in this recipe.
To add a settings bundle to your program, follow these steps. Alternatively, you can create a new settings bundle by choosing File > New File > iPhone OS > Resource > Settings Bundle:
- Create each of the property lists, one for each screen. The primary plist must be named Root.plist.
- Create a new folder in the Finder and add your property lists to that folder.
- Rename the folder to Settings.bundle. OS X warns you about the name; go ahead and confirm the rename. The folder transforms into a bundle. (To view the contents of your new bundle, right-click [Control-click] and choose Show Package Contents from the contextual pop up.)
- Drag the bundle into the File Navigator of your Xcode project.
- Optionally, create a 29x29 version of your main application icon file (typically icon.png) and add it to your project with the name Icon-Settings.png. This art is used by the Settings application to label your bundle along with the application name.
When you next run your program, the settings bundle installs and makes itself available to the Settings application. Should your source have any syntax errors, you find a blank screen rather than the settings you expect. It helps to build your settings in stages to avoid this.
Recipe 16-7. Creating a Custom Settings Pane
<?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>Title</key> <string>YOUR_PROJECT_NAME</string> <key>StringsTable</key> <string>Root</string> <key>PreferenceSpecifiers</key> <array> <dict> <key>Type</key> <string>PSGroupSpecifier</string> <key>Title</key> <string>Group</string> </dict> <dict> <key>Type</key> <string>PSTextFieldSpecifier</string> <key>Title</key> <string>Name</string> <key>Key</key> <string>name_preference</string> <key>DefaultValue</key> <string></string> <key>IsSecure</key> <false/> <key>KeyboardType</key> <string>Alphabet</string> <key>AutocapitalizationType</key> <string>None</string> <key>AutocorrectionType</key> <string>No</string> </dict> <dict> <key>Type</key> <string>PSTextFieldSpecifier</string> <key>Title</key> <string>Password</string> <key>Key</key> <string>prefs_preference</string> <key>DefaultValue</key> <string></string> <key>IsSecure</key> <true/> <key>KeyboardType</key> <string>Alphabet</string> <key>AutocapitalizationType</key> <string>None</string> <key>AutocorrectionType</key> <string>No</string> </dict> <dict> <key>Type</key> <string>PSToggleSwitchSpecifier</string> <key>Title</key> <string>Enabled</string> <key>Key</key> <string>enabled_preference</string> <key>DefaultValue</key> <true/> <key>TrueValue</key> <string>YES</string> <key>FalseValue</key> <string>NO</string> </dict> <dict> <key>Type</key> <string>PSSliderSpecifier</string> <key>Key</key> <string>slider_preference</string> <key>DefaultValue</key> <real>0.5</real> <key>MinimumValue</key> <integer>0</integer> <key>MaximumValue</key> <integer>1</integer> <key>MinimumValueImage</key> <string></string> <key>MaximumValueImage</key> <string></string> </dict> <dict> <key>Type</key> <string>PSMultiValueSpecifier</string> <key>Key</key> <string>multi_preference</string> <key>DefaultValue</key> <string>One</string> <key>Title</key> <string>MultiValue</string> <key>Titles</key> <array> <string>one</string> <string>two</string> <string>three</string> <string>four</string> </array> <key>Values</key> <array> <string>one</string> <string>two</string> <string>three</string> <string>four</string> </array> </dict> <dict> <key>Type</key> <string>PSGroupSpecifier</string> <key>Title</key> <string>Info</string> </dict> <dict> <key>Type</key> <string>PSChildPaneSpecifier</string> <key>Title</key> <string>Legal</string> <key>File</key> <string>Legal</string> </dict> </array> </dict> </plist>
Settings and Users
Although settings bundles offer a well-defined resource for developers to centralize their user-defined defaults, real-world experience suggests they’re a feature you won’t want to use. Few iOS users are aware of third-party settings outside their applications. Even fewer actually use those settings on a regular basis. Most users want to stay within the bounds of an application for all app-related tasks, including settings.
Because of this, many (if not most) App Store developers have moved away from settings bundles and brought their settings directly into the application. Adding settings views allows users to find and set preferences easily. Unfortunately, creating those screens is labor intensive and fussy.
There is, fortunately, a middle ground between relying solely on settings bundles and building your own views. The Llama Settings project at Google Code (http://code.google.com/p/llamasettings/) offers a set of classes that read property lists (including from your settings bundles), allowing you to display settings screens within your application without much extra work or overhead. The project was developed and is maintained by Scott Lawrence.
Open sourced, the Llama Settings classes provide similar kinds of display and interactive elements, including group titles, sliders, and switches. In addition, the project adds support for color selectors, URL launchers, and more. Although these items are not supported in Apple’s Settings app, you can use them within your program by defining standard property lists without further programming.
Checking User Defaults
You may solicit and set defaults via settings bundles, application-based views, code-level access, or a hybrid of these approaches. When using those settings, be aware that certain items may not yet exist. If a user hasn’t opened your settings bundle, the default settings you specified in the bundle’s property lists may not have ever been set. For most objects, you can test for this via objectForKey:. This method returns nil for nonexistent keys.
Here’s one reason a nil value may play a role in your programming: One default that you’ll always want to set through code is a “last version” key. This key records the application version that was most recently run. You’ll want to check for this default whenever your application launches.
If that default is nil, the application is a new install. You may want to prepare files and perform other setup tasks at first run. After that setup, set a value for the key, one that indicates the currently deployed app version. (And don’t forget to synchronize after setting that key.)
It doesn’t end with a check for nil, though. You’ll always know when the user has just upgraded from a previous version by checking that setting. When the last run version differs from your current version, you have the opportunity to perform any updates that bring your user into data compliance for the most recent release.