Design Considerations
The dominant design
aesthetic of iOS applications is for simple, single-purpose applications
that start fast and do one task quickly and efficiently. Being fun,
clever, and beautiful is an expected bonus. How do application
preferences fit into this design view?
You want to limit the number
of application preferences by creating opinionated software. There
might be three valid ways to accomplish a task, but your application
should have an opinion on the one best way to accomplish it, and then
should implement this one approach in such a polished and intuitive
fashion that your users instantly agree it’s the best way. Leave the
other two approaches for someone else’s application. It may seem
counterintuitive, but there is a much bigger market for opinionated
software than for applications that try to please everyone.
There are some very
important roles for application preferences. Use preferences for the
choices your users must make, rather than for all the choices they could
possibly make. For example, if you are connecting to the application
programming interface (API) of a third-party web application on behalf
of your user, and the user must provide credentials to access the
service, this is something the user must do, not just something users
might want to do differently, so it is a perfect case for storing as an
application preference.
Another strong
consideration for creating an application preference is when a
preference can streamline the use of your application; for example, when
users can record their default inputs or interests so that they don’t
have to make the same selections repeatedly. You want user preferences
that reduce the amount of onscreen typing and taps that it takes to
achieve the user’s goal for using your application.
After you decide a
preference is warranted, you have an additional decision to make. How
will you expose the preference to the user? One option is to make the
preference implicit based on what the user does while using the
application. An example of an implicitly set preference is returning to
the last state of the application. For example, suppose a user flips a
toggle to see details. When the user next uses the application, the same
toggle should be flipped and showing details.
Another option is to expose your application’s preference in Apple’s Settings application, shown in Figure 14.1.
Settings is an application built in to the iPhone. It provides a single
place to customize the iPhone. Everything from the hardware, built-in
applications from Apple, and third-party applications can be customized
from the Settings application.
A settings bundle lets you
declare the user preferences of your application so that the Settings
application can provide the user interface for editing those
preferences. There is less coding for you to do if you let Settings
handle your application’s preferences, but less coding is not always the
dominant consideration. A preference that is set once and rarely
changes, such as the username and password for a web service, is ideal
for configuring in Settings. In contrast, an option that the user might
change with each use of your application, such as the difficulty level
in a game, is not appropriate for Settings.
Reading and Writing User Defaults
Application preferences
is Apple’s name for the overall preference system by which applications
can customize themselves for the user. The application preferences
system takes care of the low-level tasks of persisting preferences to
the device, keeping each application’s preferences separate from other
applications’ preferences, and backing up application preferences to the
computer via iTunes so that users won’t lose their preferences in case
the device needs to be restored. Your interaction with the application
preferences system is through an easy-to-use API that consists mainly of
the NSUserDefaults singleton class.
The NSUserDefaults class works similarly to the NSDictionary class. The main differences are that NSUserDefaults
is a singleton and is more limited in the types of objects it can
store. All the preferences for your application are stored as key/value
pairs in the NSUserDefaults singleton.
A singleton is just an instance of the Singleton pattern, and a pattern
in programming is just a common way of doing something. The Singleton
pattern is fairly common in iOS, and it is a technique used to ensure
that there is only one instance (object) of a particular class. Most
often it is used to represent a service provided to your program by the
hardware or operating system.
Creating Implicit Preferences
In our first example, we will
create a (admittedly ridiculous) flashlight application. The application
will have an on/off switch and will shine a light from the screen when
it is on. A slider will control the brightness level of the light. We
will use preferences to return the flashlight to the last state the user
left it in.
Setting Up the Project
Create a new View-Based iPhone Application in Xcode and call it Flashlight.
Click the FlashlightViewController.h file in the Classes group and add
outlets for our on/off switch, brightness slider, and light source. Add
an action called setLightSourceAlpha
that will respond when toggling the switch or sliding the brightness
control. The FlashlightViewController.h file should read as shown in Listing 1.
Listing 1.
#import <UIKit/UIKit.h>
@interface FlashlightViewController : UIViewController {
IBOutlet UIView *lightSource; IBOutlet UISwitch *toggleSwitch; IBOutlet UISlider *brightnessSlider;
}
@property (nonatomic, retain) UIView *lightSource; @property (nonatomic, retain) UISwitch *toggleSwitch; @property (nonatomic, retain) UISlider *brightnessSlider;
-(IBAction) setLightSourceAlphaValue;
@end
|
Next, edit the FlashlightViewController.m implementation file and add corresponding @synthesize directives for each property, following the @implementation line:
@synthesize lightSource;
@synthesize toggleSwitch;
@synthesize brightnessSlider;
To make sure we don’t forget to clean up the retained objects later, edit the dealloc method to release the lightSource, toggleSwitch, and brightnessSlider objects:
- (void)dealloc {
[lightSource release];
[toggleSwitch release];
[brightnessSlider release];
[super dealloc];
}
Now, let’s lay out the UI for the flashlight.
Creating the Interface
Open Interface Builder by double-clicking the FlashlightViewController.xib file in the Resources group.
1. | In Interface Builder, click the empty view and open the Attributes Inspector (Command+1).
|
2. | Set the background color of the view to black. (We want our flashlight to have a black background.)
|
3. | Drag a UISwitch from the Library onto the bottom left of the view.
|
4. | Drag a UISlider to the bottom right of the view. Size the slider to take up all the horizontal space not used by the switch.
|
5. | Finally, add a UIView
to the top portion of the view. Size it so that it is full width and
takes up all the vertical space above the switch and slider. Your view
should now look like Figure 2.
|
Connect the Outlets and Action
The
code we will write to operate the flashlight and deal with the
application preferences will need access to the switch, slider, and
light source. Control-drag from the File’s Owner icon and connect the lightSource IBOutlet to the new UIView, the toggleSwitch IBOutlet to the UISwitch, and the brightnessSlider IBOutlet to the UISlider.
In addition to being able to
access the three controls, our code needs to respond to changes in the
toggle state of the switch and changes in the position of the slider.
Select the UISwitch and open the Connections Inspector (Command+2). Connect the UISwitch to the setLightSourceAlphaValue method by dragging from the circle beside the Value Changed event to the File’s Owner icon. Choose setLightSourceAlphaValue when prompted to make the connection, as seen in Figure 3.
Repeat this process for the UISlider, having its Value Changed event also call the setLightSourceAlphaValue method. This ensures immediate feedback when the user adjusts the slider value.
Save your work, and then switch back to Xcode.
Implementing the Application Logic
What can I say? Flashlights don’t have much logic!
When the user toggles the
flashlight on or off and adjusts the brightness level, the application
will respond by adjusting the alpha property of the lightSource
view. The alpha property of a view controls the transparency of the
view, with 0.0 being completely transparent and 1.0 being completely
translucent. The lightSource view is white and is on top of the black background. When the lightSource
view is more transparent, more of the black will be showing through and
the flashlight will be darker. When we want to turn the light off, we
just set the alpha property to 0.0 so that none of the white background
of the lightSource view will be showing.
To make the flashlight work, we can add this method in Listing 2 to FlashlightViewController.m.
Listing 2.
-(IBAction) setLightSourceAlphaValue { if (toggleSwitch.on) { lightSource.alpha = brightnessSlider.value; } else { lightSource.alpha = 0.0; } }
|
This simple method checks the on property of the toggleSwitch object, and, if it is on, sets the alpha property of the lightSource UIView to the value property of the slider. The slider’s value
property returns a floating-point number between 0 and 100, so this is
already enough code to make the flashlight work. You can run the project
yourself and see.
Storing the Flashlight Preferences
We don’t just want the
flashlight to work; we want it to return to its last state when the user
uses the flashlight application again later. We’ll store the on/off
state and the brightness level as implicit preferences. First we need
two constants to be the keys for these preferences. Add these constants
to the top of the FlashlightViewController.h interface file:
#define kOnOffToggle @"onOff"
#define kBrightnessLevel @"brightness"
Then we will persist the two values of the keys in the setLightSourceAlphaValue event of the FlashlightViewController. We will get the NSUserDefault singleton using the standardUserDefaults method and then use the setBool and setFloat methods. Because NSUserDefaults is a singleton, we are not creating it and are not responsible for managing its memory. We will wrap up by using the NSUserDefaults method synchronize to make sure that our settings are stored immediately.
Update the setLightSourceAlphaValue method, as shown in Listing 3.
Listing 3.
-(IBAction) setLightSourceAlphaValue {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setBool:toggleSwitch.on forKey:kOnOffToggle]; [userDefaults setFloat:brightnessSlider.value forKey:kBrightnessLevel]; [userDefaults synchronize];
if (toggleSwitch.on) { lightSource.alpha = brightnessSlider.value; } else { lightSource.alpha = 0.0; } }
|
It can still be useful to know,
however, and the answer is that our preferences are stored in a plist
file. If you are an experienced Mac user you may already be familiar
with plists, which are used for Mac applications, too. When running on a
device, the plist will be local to the device, but when we run our
application in the iPhone Simulator, the simulator uses our computer’s
hard drive for storage, making it easy for us to peek inside the plist.
Run the Flashlight application in the iPhone Simulator, and then use Finder to navigate to /Users/<your username>/Library/Application Support/iPhone Simulator/<Device OS Version>/Applications.
The directories in Applications are generated globally unique IDs, but
it should be easy to find the directory for Flashlight by looking for
the most recent Data Modified. You’ll see Flashlight.app in the most
recently modified directory, and you’ll see the
com.yourcompany.Flashlight.plist inside the ./Library/Preferences
subdirectory. This is a regular Mac plist file, so when you double-click
it, it will open with the Property List Editor application and show you
the two preferences for Flashlight.
Reading the Flashlight Preferences
Now our application is
writing out the state of the two controls anytime the user changes the
flashlight settings. So, to complete the desired behavior, we need to
read in and use the preferences for the state of the two controls
anytime our application launches. For this, we will use the viewDidLoad method, which is provided for us by Xcode as a commented-out stub, and the floatForKey and boolForKey methods of NSUserDefaults. Uncomment viewDidLoad and get the NSUserDefaults
singleton in the same way as before, but this time we will set the
value of the controls from the value returned from the preference rather
than the other way around.
In the FlashlightViewController.m file in the Classes group, implement viewDidLoad, as shown in Listing 4
Listing 4.
- (void)viewDidLoad { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; brightnessSlider.value = [userDefaults floatForKey:kBrightnessLevel]; toggleSwitch.on = [userDefaults boolForKey:kOnOffToggle]; if ([userDefaults boolForKey: kOnOffToggle]) { lightSource.alpha = [userDefaults floatForKey:kBrightnessLevel]; } else { lightSource.alpha = 0.0; } [super viewDidLoad]; }
|
That’s
all there is to it. All we need now is a snazzy application icon, and
we, too, can make millions in the App Store with our Flashlight
application (see Figure 4).
If you’re running iOS 4 and
press the Home button, be aware that your application won’t quit; it
will be suspended in the background. To fully test the flashlight app,
be sure to use the iOS Task Manager to force the application completely
closed, and then verify that your settings are restored when it
relaunches fresh.
While we are waiting for the
cash to pour in (it may be awhile), let’s look at an application where
the user takes more direct control of the application’s preferences.