Implementing System Settings
One option to consider for
providing application preferences is to use the Settings application.
You do this by creating and editing a settings bundle for your
application in Xcode rather than by writing code and designing a UI, so
this is a very fast and easy option.
For our second
application of the hour, we’ll create ReturnMe, an application that
tells someone who finds a lost device how to return it to its owner. The
Settings application will be used to edit the contact information of
the owner and to select a picture to evoke the finder’s sympathy.
Setting Up the Project
As we frequently do, begin by creating a new View-based iPhone application in Xcode called ReturnMe.
We want to provide the
finder of the lost device with a sympathy-invoking picture and the
owner’s name, email address, and phone number. Each of these items will
be configurable as an application preference, so we’ll need outlets to
set the values of a UIImageView and three UILabels.
Open the
ReturnMeViewController.h file in the Classes group and add outlets and
properties for each control. The completed ReturnMeViewController.h file
should look like Listing 5.
Listing 5.
#import <UIKit/UIKit.h>
@interface ReturnMeViewController : UIViewController { IBOutlet UIImageView *picture; IBOutlet UILabel *name; IBOutlet UILabel *email; IBOutlet UILabel *phone; }
@property (nonatomic, retain) UIImageView *picture; @property (nonatomic, retain) UILabel *name; @property (nonatomic, retain) UILabel *email; @property (nonatomic, retain) UILabel *phone;
@end
|
After updating the interface file, add corresponding @synthesize lines within the ReturnMeViewController.m file for each property:
@synthesize picture;
@synthesize name;
@synthesize email;
@synthesize phone;
And, of course, release all these objects in the dealloc method:
- (void)dealloc {
[picture release];
[name release];
[email release];
[phone release];
[super dealloc];
}
We want a few images that will
help goad our Good Samaritan into returning the lost device rather than
selling it to Gizmodo. Drag the dog1.png, dog2.png, and coral.png
files from the project’s Images folder into the Resources group. When
you drag the images into Xcode, make sure you click the option to copy
them into the destination group’s folder so that copies of the images
will be placed in your Xcode project’s directory.
Remember, to support
the higher resolution display of the iPhone 4, all you need to do is
create images with twice the horizontal and vertical resolution as your
standard iPhone image resources, include a @2x suffix on the filename,
and add them to your project. The developer tools and iOS take care of
the rest!
Creating the Interface
Now let’s lay out the
ReturnMe application’s UI. Open Interface Builder by double-clicking the
ReturnMeViewController.xib file in the Resources group:
1. | Open the XIB file’s view.
|
2. | Drag three UILabels
onto the view. Click each label and open the Attributes Inspector
(Command+1), and set the text to a default value of your choosing for
name, email, and phone number.
|
3. | Drag a UIImageView to the view. Size the image view to take up the majority of the iPhone’s display area.
|
4. | With
the image view selected, open the Attributes Inspector (Command+1). Set
the mode to be Aspect Fill, and pick one of the animal images you added
to the Xcode project from the Image drop-down.
|
5. | Add some additional UILabels to explain the purpose of the application and labels that explain each preference value (name, email, and phone number).
|
As long as you have the three labels and the image view in your UI, as seen in Figure 5, you can design the rest as you see fit. Have fun with it!
When you’re finished building the interface, connect the UIImageView and three UILabels to their corresponding outlets—picture, name, email, and phone.
There aren’t any actions to connect, so just Control-drag from the
File’s Owner icon to each of these interface items, choosing the
appropriate outlet when prompted.
Now that the interface is
built, we’ll create the Settings Bundle, which will enable us to
integrate with the iPhone’s Settings application.
Creating the Settings Bundle
Create
a new settings bundle in Xcode by selecting File, New File from the
menu and selecting Settings Bundle from the iOS Resource group in the
sidebar, as shown in Figure 6.
Keep
the default name of Settings.bundle when you create it. Drag the newly
created settings bundle into the Resources group if it does not get
created there.
The file that controls
how our ReturnMe application will appear in the Settings application is
the Root.plist file in the settings bundle. We can edit this file with
the Property List Editor that is built in to Xcode. We will add
preference types to it (see Table 1) that will be read and interpreted by the Settings application to provide the UI to set our application’s preferences.
Table 1. Preference Types
Type | Key | Description |
---|
Text field | PSTextFieldSpecifier | Editable text string |
Toggle switch | PSToggleSwitchSpecifier | On/off toggle button |
Slider | PSSliderSpecifier | Slider across a range of values |
Multivalue | PSMultiValueSpecifier | Drop-down value picker |
Title | PSTitleValueSpecifier | Read-only text string |
Group | PSGroupSpecifier | Title for a logical group of preferences |
Child pane | PSChildPaneSpecifier | Child preferences page |
The ReturnMe preferences
will be grouped into three groups: Sympathy Image, Contact Information,
and About. The Sympathy Image group will contain a multivalue preference
to pick one of the images, the Contact Information group will contain
three text fields, and the About group will link to a child page with
three read-only titles.
Expand the Settings.bundle in Xcode and click the Root.plist file.
You’ll see a table of three columns: Key, Type, and Value. Expand the PreferencesSpecifiers
property in the table, and you’ll see a series of four dictionary
properties. These are provided by Xcode as samples, and each of them
will be interpreted by Settings as a preference. You will follow the
simple schema in the Settings Application Schema Reference in the iOS Reference Library to set all the required properties, and some of the optional properties, of each preference.
Expand the first dictionary property under PreferenceSpecifiers called Item 0, and you’ll see that its Type is PSGroupSpecifier. This is the correct Type to define a preference group, but change the Title property’s value to Sympathy Image by clicking it and typing the new title. Expand the second item (Item 1) and you’ll see that its Type is PSTextFieldSpecifier. Our Sympathy Image will be selected as a multivalue, not a text field, so change the Type to PSMultiValueSpecifier. Change the Title to Image Name, the Key to picture, and the DefaultValue to Dog. The remaining four keys under Item 1 apply to text fields only, so delete them by selecting them and pressing the Delete key.
The values for a
multivalue picker come from two arrays, an array of item names and an
array of item values. In our case, the name and value arrays will be the
same, but we still must provide both of them. To add another property
under DefaultValue, click the plus sign at the end of the row (see Figure 7) to add another property at the same level.
The new item will have the default name of New Item, so change that to Values. The Type column defaults to String, so change it to Array.
Each of the three possible image names needs a property under the
Values property. Expand the Values item’s disclosure triangle, and
notice that the plus sign at the end of the row changes to an icon with
three lines (see Figure 8).
This icon adds child properties, rather than properties at the same
level. Click the icon three times to add properties that’ll be called
Item 0, Item 1, and Item 2.
Change the Value of the three new child properties to Dog, Mean Dog, and Coral. Repeat this step to add a peer of Values called Titles. Titles is also an Array type and has the same three String type children with the same values, as shown in Figure 9.
The third property (Item 2) in our plist PreferenceSpecifiers should be a PSGroupSpecifier with a title of Contact Information. Change the Type and Title of Item 2 and remove the extra items.
The fourth property (Item 3) is the name preference. Change the Type to PSTextFieldSpecifier, the Key to name, and the DefaultValue to Your Name with a Type of String. Add three more keys to Item 3. These should be String types and are optional parameters that set up the keyboard for text entry. Set the keys to KeyboardType, AutocapitalizationType, and AutocorrectionType, and the values to Alphabet, Words, and No, respectively.
You can test your settings
so far by saving the plist, building and running the ReadMe application
in the iPhone Simulator, exiting the application with the Home button,
and then running the Settings application in the simulator. You should
see a Settings selection for the ReturnMe application and settings for
the Sympathy Image and Name.
Add two more PSTextFieldSpecifier
preferences to the plist; mirror what you set up for the Name
preference: one for email and one for phone number. Use the keys of email and phone, and change the KeyboardType to EmailAddress and NumberPad, respectively.
The final preference is About,
and it opens a child preference pane—we’ll add two more items to
accomplish this. First, add a new item—Item 6—to the plist. Configure
the item as a dictionary; then add a Type of PSGroupSpecifier and a Title of About ReturnMe.
Next, add Item 7, configured as a dictionary. Expand the new item and set a property with a Type of PSChildPaneSpecifier, a Title of About, and a String property with a Key of File and a Value of About.
The child pane element assumes the value of File exists as another
plist in the settings bundle. In our case, this is a file called
About.plist.
The easiest way to create
this second plist file in the settings bundle is by copying the
Root.plist file we already have. Right-click the Root.plist file in
Xcode, and select the Open with Finder menu option. This opens the plist
in the external Property List Editor. Select File, Save As and change
the name to About.plist
before clicking Save. Xcode won’t immediately notice that your settings
bundle has a new plist file. Collapse and expand Settings.bundle to
refresh the contents.
Edit About.plist to have one group property titled About ReturnMe and three PSTitleSpecifier properties for Version, Copyright, and Website. PSTitleSpecifierDefaultValue. If you have any difficulties setting up your plist files, compare your properties have four properties: Type, Title, Key, and preferences UI to Figure 10 and your plists to the plists in the settings bundle in the project’s source code to see where you might have made a misstep.
Connecting Preferences to the Application
We have four preferences we
want to retrieve from the preferences database: the selected image and
the device owner’s name, email, and alternative phone number. Add a key
constant for each of these to the top of the ReturnMeViewController.h
file in Xcode:
#define kName @"name"
#define kEmail @"email"
#define kPhone @"phone"
#define kReward @"reward"
#define kPicture @"picture"
We have now bundled up
our preferences so that they can be set by the Settings application, but
our ReturnMe application also has to be modified to use the
preferences. We do this in the ReturnMeViewController.m file’s viewDidLoad event. Here we will call a helper method we write called setValuesFromPreferences. Our code to use the preference values with the NSserDefaults
API looks no different from the Flashlight application. It doesn’t
matter if our application wrote the preference values or if the Settings
application did; we can simply treat NSUserDefaults like a dictionary and ask for objects by their key.
We provided default values in
the settings bundle, but it’s possible the user just installed ReturnMe
and has not run the Settings application. We should provide the same
default settings programmatically to cover this case, and we can do that
by providing a dictionary of default preference keys and values to the NSUserDefaults registerDefaults method. Add the methods in Listing 6 to the ReturnMeViewController.m file.
Listing 6.
- (NSDictionary *)initialDefaults { NSArray *keys = [[[NSArray alloc] initWithObjects: kPicture, kName, kEmail, kPhone, nil] autorelease]; NSArray *values = [[[NSArray alloc] initWithObjects: @"Dog", @"Your Name", @"you@yours.com", @"(555)555-1212", nil] autorelease]; return [[[NSDictionary alloc] initWithObjects: values forKeys: keys] autorelease]; }
-(void)setValuesFromPreferences {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults registerDefaults: [self initialDefaults]]; NSString *picturePreference = [userDefaults stringForKey:kPicture]; if ([picturePreference isEqualToString:@"Dog"]) { picture.image = [UIImage imageNamed:@"dog1.png"]; } else if ([picturePreference isEqualToString:@"Mean Dog"]) { picture.image = [UIImage imageNamed:@"dog2.png"]; } else { picture.image = [UIImage imageNamed:@"coral.png"]; }
name.text = [userDefaults stringForKey:kName]; email.text = [userDefaults stringForKey:kEmail]; phone.text = [userDefaults stringForKey:kPhone];
}
|
With
this supporting code in place, we have the ability to set some default
preferences, and load preferences that are configured in the iPhone
Settings application. That said, we still need to load the preferences
when the application starts. Edit ReturnMeViewController.m, implementing
the viewDidLoad method to invoke setValuesFromPreferences:
- (void)viewDidLoad {
[self setValuesFromPreferences];
[super viewDidLoad];
}
Build and run the
modified ReturnMe application and switch back and forth between the
Settings and ReturnMe applications. You can see that with very little
code on our part we were able to provide a sophisticated interface to
configure our application. The Settings application’s plist schema
provides a fairly complete way to describe the preference needs of our
application.