iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (65 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
10.94Mb size Format: txt, pdf, ePub
Using NSUserDefaults

What we need to do now is store a value to specify the map type that the user last selected so that the chosen type will automatically be displayed when the user launches the application again. This value will be stored in an instance of
NSUserDefaults
.

 

Every application has an instance of
NSUserDefaults
that it can access by sending the class message
standardUserDefaults
to the
NSUserDefaults
class. This instance of
NSUserDefaults
is like an
NSMutableDictionary
; you can access objects in it using a key. It is also automatically read from disk when the application first accesses it and written to disk when it is modified.

 

The keys of an
NSUserDefaults
are always of type
NSString
. A key identifies a preference. An object represents the value of a preference. These objects must be property list serializable or primitives. For example, if you created a key

Text Size

and assigned the integer 16, it would mean the user’s preferred text size is 16 points.

 

But that

Text Size

key is a completely hypothetical example. There isn’t a built-in key that magically resizes all of your text. In fact, there are no built-in keys at all. Instead, you create your own keys specific to your application and give them values that mean something to your application.

 

The key you will create for
Whereami
is a string, and it will be used for both reading and setting the value. You will define it as a static variable so that you can pass a variable as the key instead of a hard-coded string. (If you mistype a variable name, the compiler will give you an error. But it can’t help if you’ve mistyped a string.)

 

At the top of
WhereamiViewController.m
, declare a new static variable to hold the preference name for the map type.

 
NSString * const WhereamiMapTypePrefKey = @"WhereamiMapTypePrefKey";
@implementation WhereamiViewController

Notice that the key is the name of the application, the name of the preference, and the words
PrefKey
. This is the typical pattern for naming preference keys.

 

To add or change the value of a preference in an
NSUserDefaults
, you use
setObject:forKey:
just like you would with an
NSMutableDictionary
. In addition,
NSUserDefaults
has some convenience methods for putting primitives into preferences, like
setInteger:forKey:
.

 

In
WhereamiViewController.m
, add the following line of code to
changeMapType:
.

 
- (IBAction)changeMapType:(id)sender
{
    [[NSUserDefaults standardUserDefaults]
                    setInteger:[sender selectedSegmentIndex]
                        forKey:WhereamiMapTypePrefKey];
    switch([sender selectedSegmentIndex])
    {
        case 0:
        {
            [worldView setMapType:MKMapTypeStandard];
        }break;
        case 1:
        {
            [worldView setMapType:MKMapTypeSatellite];
        }break;
        case 2:
        {
            [worldView setMapType:MKMapTypeHybrid];
        }break;
    }
}
 

Now, whenever the user changes the map type, that value will be written to the
NSUserDefaults
, which will then be saved to disk. When the
NSUserDefaults
saves its data to disk, it saves it to the
Library/Preferences
directory. The name of that file will be the bundle identifier for your application.

 

When your application firsts asks for the
standardUserDefaults
, it will load this file from disk, and all of the saved preferences will be available to your application. In
WhereamiViewController.m
, update the method
viewDidLoad
to read in this value and set the map type accordingly.

 
- (void)viewDidLoad
{
    [worldView setShowsUserLocation:YES];
    
NSInteger mapTypeValue = [[NSUserDefaults standardUserDefaults]
                                integerForKey:WhereamiMapTypePrefKey];
    // Update the UI
    [mapTypeControl setSelectedSegmentIndex:mapTypeValue];
    // Update the map
    [self changeMapType:mapTypeControl];
}
 

Build and run
Whereami
and change the map type. Exit the application by pressing the Home button and kill it from
Xcode
. Then, relaunch it, and the map will display the type of map you previously selected.

 

When your application first launches, the value for the key
WhereamiMapTypePrefKey
does not exist, and it defaults to 0. For this application, that works fine, but some preferences may need a temporary, non-zero default value (i.e., a

factory setting

) for the application to run correctly. These temporary defaults are placed in the
registration domain
of
NSUserDefaults
. Any preferences set by the user are stored in a different domain, the
application domain
.

 

By default, the application domain is empty: there are no keys and no values. The first time a user changes a setting, a value is added to the application domain for the specified key. When you ask the
NSUserDefaults
for the value of a preference, it first looks in the application domain. If there is a value for that key, then the user has set a preference, and the
NSUserDefaults
returns that value. If not, the
NSUserDefaults
looks in the registration domain and finds the temporary default.

 

The application domain is always saved to disk; that’s why it remembers user preferences on the next launch. The registration domain is not, and its values must be set every time the application launches. To set the values of the registration domain, you create an
NSDictionary
with a key-value pair for each preference you plan on using in your application. Then, you send the dictionary as an argument to the method
registerDefaults:
of
NSUserDefaults
.

 

Typically, you send the
registerDefaults:
message before any object is able to access the instance of
NSUserDefaults
. This means before the instance of the application delegate is created. What comes before the creation of the
WhereamiViewController
? The creation of the
WhereamiViewController
class
. Like any object, a class also must be initialized before it can receive messages. So, after a class is created but before it receives its first message, it is sent the message
initialize
.

 

In
WhereamiViewController.m
, override the class method
initialize
to register defaults, including setting the map type preference to 1.

 
+ (void)initialize
{
    NSDictionary *defaults = [NSDictionary
                            dictionaryWithObject:[NSNumber numberWithInt:1]
                                          forKey:WhereamiMapTypePrefKey];
    [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
}
 

Delete the application from your device to remove the previously saved preferences. Then, build and run again. The first time the application launches, the default satellite map will be displayed. If you change the map type, the preference is added to the application domain, and your application will use that value from then on. The default value in the registration domain will be ignored.

 

NSUserDefaults
is a simple class but a useful one. In addition to storing preferences, many developers use
NSUserDefaults
to store little pieces of information that are important but don’t really need to be kept in distinct files. For example, if you want to display an alert to a user every 3rd time the application is launched, you could store the number of times the application has been launched in the
NSUserDefaults
.

 
Silver Challenge: Initial Location

Every time the application finds a new location, note the latitude and longitude in
NSUserDefaults
. When the application launches, zoom in on this coordinate. Make sure you register a default!

 
Gold Challenge: Concise Coordinates

The silver challenge revealed that you would have to separate the latitude and longitude into separate key-value pairs in
NSUserDefaults
. Make it so both the latitude and longitude are stored as one value under one key. (Hint: You will need to use
NSKeyedArchiver
and
NSKeyedUnarchiver
, and not
NSUserDefaults

setObject:forKey:
.)

 
For the More Curious: The Settings Application

Every iOS device has a
Settings
application. Applications that register some or all of their preferences with
Settings
get an entry in this application where those preferences can be changed. However, most applications do not use
Settings
to store their preferences: why leave the application, find
Settings
, change a value, and then re-open the application when you could have your own built-in interface?

 

But, in case it’s something you want to do, here’s how. To register for an entry in
Settings
, you add a
Settings.bundle
to your application. This bundle contains a property list that has an entry for each preference you want to expose in the
Settings
application. (There is a template for this bundle when you create a new file.)

 

Each entry contains a key that is a string that matches the name of the preference keys you use with
NSUserDefaults
. Additionally, you set a
preference specifier
key with one of the pre-defined constants. These constants indicate the type of control that will appear for this preference in the
Settings
application. They are things like text fields, switches, and sliders. You also add a key for a default value. Some preference specifiers require you to add additional keys. For example, a slider preference will need a minimum and a maximum value.

 

If you choose to use a settings bundle, you must take care to respect the changes made in the
Settings
application when transitioning between application states. If your application is terminated and then the user changes a value in
Settings
, those changes will be written to disk, and the next time your application launches, it will get the correct values. However, most iOS applications are not terminated when the user presses the Home button. Thus, when your application returns from being suspended, you should check to see if any of the preferences were changed while it was suspended.

 

An application returning from the suspended state that has had preferences changed will be notified via the
NSNotificationCenter
. You can register for the
NSUserDefaultsDidChangeNotification
notification to be informed of these changes. (You typically register for this notification when the application first launches.)

 
19
Touch Events and UIResponder

In
Chapter 6
, you created a
UIScrollView
that handled touch events to scroll a view and even handled a multi-touch event to zoom. The
UIScrollView
class makes scrolling and zooming easy to implement. But what if you want to do something else, something special or unique, with touch events?

 

In this chapter, you are going to create a view that lets the user draw lines by dragging across the view (
Figure 19.1
). Using multi-touch, the user will be able to draw more than one line at a time. Double-tapping will clear the screen and allow the user to begin again.

 

Figure 19.1  A drawing program

 
 

Other books

The Theta Patient by Chris Dietzel
Redemption by Gordon, H. D.
The Third Gate by Lincoln Child