A property list is a graph of objects, where every object is one the following classes:
n NSDictionary
n NSArray
n NSString
n NSNumber (any integer, floating point, or Boolean value)
n NSDate
n NSData
While a property list can be a single string, it is most often a dictionary that contain strings, numbers, dates, or other arrays and dictionaries. Instances of these classes are called property list objects.
Seriously, that’s it.
Serializing Property Lists
Property lists are used throughout iOS because they are flexible, universal, and easily serialized. In this case, “serialize” (the Cocoa term) means “serialize” (the computer science term). Cocoa uses the term serialization tomean converting a property list into a transportable stream of bytes. You don’t often serialize property lists yourself, but they are regularly serialized behind the scenes.
A serialized property list written to a file is called a property list file, often a .plist file. Xcode includes a
property list editor so you can directly create and modify the contents of a property list file. You’ll use the property listeditor later in this chapter.
For the Wonderland app, I wrote a Mac (OS X) utility application that generated the Characters.nsarray resourefile. That was a property list (an array of dictionaries containing strings), serialized in the XML format, and
writtento a property list file. Later, you added that as a resource file, which your app turned back into an NSArray object by deserializing the file.
User Defaults
One of the premier uses of property list objects is in the user defaults. The user defaults is a dictionary of property list objects you can use to store small amounts of persistent information, such as
preferences and displaystate. You can store any property list value you want into the user
defaults (NSUserDefaults) object, and later retrieve it. The values you store there are serialized and preserved between runs of your app.
A user defaults (NSUserDefaults) object is created when your app starts. Any values you stored there the last time are deserialized and become immediately available. If you make any changes to the user defaults, they areautomatically serialized and saved so they’ll be available the next time your app runs.
Using NSUserDefaults is really simple. You obtain your app’s singleton user defaults object using [NSUserDefaults standardUserDefaults]. You send it “set” messages to store values
(-setInteger:forKey:, -setObject:forKey:, -setBool:forKey:, and so on). You retrieve values using the “get” messages (-integerForKey:, -objectForKey:, -boolForKey:, and so on).
Making Pigeon Remember
You’re going to use user defaults to give Pigeon some long-term memory. When you add user defaults to an app you need to consider:
n What values to store
n What property list objects and keys you will use
n When to store the values
n When to retrieve the values
Each decision affects subsequent ones, so start at the top. For Pigeon, you want it to remember: The remembered map location (duh)
The map type (plain, satellite, or hybrid)
The tracking mode (none or follow heading)
The next step is to decide what property list objects you’re going to use to represent these properties. The map type and tracking mode are easy; they’re both integer properties, and you can store any integer value directly in the user defaults.
The MKPointAnnotation object that encapsulates the map location, however, isn’t a property list object
and can’t be stored directly in the user defaults. Instead, its significant properties need to be converted
into property listobjects, which can be stored. The typical technique is to turn your information into either a string or a dictionary of property list objects, both of which are compatible with user defaults.
For Pigeon, you’re going to convert the annotation into a dictionary containing three values: its latitude,
itslongitude, and its title. This is enough information to reconstruct the annotation when the app runs again.
You also have to pick keys to identify each value stored. At the top-level, you want to choose keys that won’t be confused with any keys iOS might be using. A number of iOS frameworks also use your app’s user defaults to preserve information. The simplest technique is to use the same class prefix that your project uses. For example, it’s unlikely the keys “HPMapType” and “HPFollowHeading” would conflictwith any reserved keys. Keys used for values in sub-dictionaries can be anything you want.
Minimizing Updates and Code
With the first part out of the way, you can now turn your attention to the much subtler problem of deciding when and where to preserve your values in the user defaults, and when to get them back out again.
Tackle the storage problem first. As a rule, you want to make updates to the user defaults as infrequently as possible, while still keeping your code simple. The common solutions are:
n Capture the value when it changes
n Capture the value at some dependable exit point
The first solution is perfect for Pigeon. It only saves three values, and none of those change that often. The user might change map type and heading from time to time, but they’re unlikely to fiddle with those settings a dozen times a minute. Likewise, the user will save a location when they arrive somewhere, but won’t save another location until they’ve traveled someplace else.
The reason you want to limit user default updates is that every change triggers a chain of events that results in a fair amount of work occurring in the background. It’s something to avoid, as long as it doesn’t overly complicateyour design. A good design will minimize updates with a minimal amount of code. When you start working with cloud-based storage (later in this chapter) it’s even more important to avoid gratuitous changes.
On the other hand, some values you want to preserve might change all the time or in many different places. For example, remembering the playback location of an audio book is something that changes constantly. It would beludicrous to capture the playback position every second the audio was playing. Instead, it makes a lot more sense to simply note the user’s current playback position when they exit the app. You’ll explore that technique laterin this chapter.
You’re going to start by preserving the map type and tracking mode, because these are the simplest.
Then you’ll tackle preserving and restoring the map location.
Defining Your Keys
This tutorial starts with the version of Pigeon in the exercise for Chapter 17. You’ll find that version in the Learn iOS Development Projects ➤ Ch 17 ➤ Pigeon E1 folder. If you came up with your own solution to the exercise, youshould have no problem adapting this code to your app.
Begin by defining the keys used to identify values in your user defaults. Select the
HPViewController.h file and add these three constants:
#define kPreferenceMapType @"HPMapType"
#define kPreferenceHeading @"HPFollowHeading"
#define kPreferenceSavedLocation @"HPLocation"
Writing Values to User Defaults
Locate the code where the map type and tracking mode get changed. If you’re working with the version of Pigeon I wrote for Chapter 17, that code is in HPMapOptionsViewController.m. Add this
#import statement so the code can use the key constants you just defined:
#import "HPViewController.h"
Now find the code where each setting gets changed. In HPMapOptionsViewController that happens in the -changeMapStyle: and -changeHeading: methods. Change the code so it looks like this
(new code in bold):
- (IBAction)changeMapStyle:(id)sender
{
MKMapType mapType = self.mapStyleControl.selectedSegmentIndex; self.mapView.mapType = mapType;
[[NSUserDefaults standardUserDefaults] setInteger:mapType forKey:kPreferenceMapType];
}
- (IBAction)changeHeading:(id)sender
{
MKUserTrackingMode tracking = self.headingControl.selectedSegmentIndex+1; self.mapView.userTrackingMode = tracking;
[[NSUserDefaults standardUserDefaults] setInteger:tracking forKey:kPreferenceHeading];
}
The change is straightforward, and you should have no problem adapting the same idea to your
own app. When a setting is changed, the new value is also stored in the user defaults. That’s all you have to do. NSUserDefaults takes care of everything else: converting the simple integer value into the appropriate property list(NSNumber) object, serializing the values, and storing them so they’ll be available the next time your app runs.
That’s the first half. Now you need to add the code to retrieve these saved values and restore the map options when your app starts.
Getting Values from User Defaults
Select the HPViewController.m file and locate the -viewDidLoad method.
Replace the [_mapView set UserTrackingMode:MKUserTrackingModeFollow] statement with this code:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
_mapView.mapType = [userDefaults integerForKey:kPreferenceMapType]; NSUInteger trackingMode;
if ([userDefaults objectForKey:kPreferenceHeading]!=nil) trackingMode = [userDefaults integerForKey:kPreferenceHeading];
else
trackingMode = MKUserTrackingModeFollow;
_mapView.userTrackingMode = trackingMode;
This new code retrieves the integer values for the map type and tracking mode from the user defaults and uses them to restore those properties before the map is displayed. The result is that when the user runs the app andchanges the map type, every time they launch the app after that the map type will be the same.
But there’s a hitch. The very first time the app is run—or if the user never changes the map type or tracking mode—there are no values at all for those keys in the user defaults. If you request the property list object for a non-existent key, user defaults will return nil. If you request a scalar value (Boolean, integer, or floating-point) user defaults will return NO, 0, or 0.0. Here are three ways of dealing with this situation:
n Choose your values so that nil, NO, 0, or 0.0 is the default
n Test to see if user defaults contains a value for that key
n Register a default value for that key
The map type property adopts the first solution. Conveniently, the initial map type in Pigeon is
MKMapTypeStandard, whose integer value is 0. So if there is no value in user defaults for the
kPreferenceMapType key, itreturns a 0 and sets the map type to standard—which is perfect.
The tracking mode isn’t so lucky. The initial tracking mode Pigeon uses is MKUserTrackingModeFollow,
whose integer value is 1. If there’s no value for the kPreferenceHeading key, you don’t want to set
trackingMode toMKUserTrackingModeNone (0) by mistake.
Instead, the code uses the second solution. It first gets the property list (NSNumber) object for that key. If there’s no value for that key, user defaults returns nil and you know that a tracking value has never been set. You use thisknowledge to either restore the user-selected mode or set the correct default.
That’s everything you need to preserve and restore these map settings. It’s time to test it out, but that will require a little finesse.
Testing User Defaults
Using either a provisioned device or the simulator, run your updated Pigeon app. Tap the settings button and change the map type and tracking mode, as show in Figure 18-1.
This will update the user defaults with the new values, but those values may, or may not, be saved in persistent storage yet. That’s because the user defaults tries to be as efficient as possible and may wait for additional changes before beginning the serialization andstorage process.
Figure 18-1. Testing the map settings
One way to get its attention is to push your app into the background. Do this by tapping the home button or use the Hardware ➤ Home command in the simulator, shown in the third image in Figure 18-1. When your app entersthe background, it doesn’t immediately stop running, but it prepares itself for that eventuality. One of those steps is to serialize and preserve all of your user defaults.
With your user defaults safely stored, you can now stop your app and start it running again. Switch back to Xcode and click the stop button. Once the app stops, click the run button. The app starts up again. This time, it loadsthe map type and tracking mode from the saved user defaults and restores those properties. When theview controller loads, the map is exactly as the user left it last time.
Congratulations, you’ve learned the basics of preserving and restoring values in the user defaults.
In the next few sections you’re going to refine your technique a little, and deal with the (slightly) more complexproblem of preserving and restoring the user’s saved map location.
Registering Default Values
The code to restore the tracking mode is awfully ugly. Well, maybe not “awfully ugly,” but it’s a little ugly. If you had a dozen of these settings to restore, you’d have a lot of repetitive code to write. Fortunately, there’s a moreelegant solution.
Your app can register a set of default values for specific keys in user defaults—yes, they’re default defaults. When your code requests a value ([userDefaults integerForKey:kPreferenceHeading]), the user defaults checks to see if avalue for that key has been previously set. If not, it returns a default value. For integers that value is 0—unless you’ve specified something else. You do that using the -registerDefaults: method.
Select the HPAppDelegate.m implementation file. This is your app’s delegate| object. It receives a lot of messages about the state of your app. One of those is the -application:willFinishLaunching WithOptions: method.
This is thefirst message your app object receives, and is normally the first opportunity for code that you’ve written to run.
Add this #import towards the top of the file, so your new code can use your key constants:
#import "HPViewController.h"
In the @implementation section, add this method (or update it if it already exists):
-(BOOL) application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ kPreferenceHeading: @(MKUserTrackingModeFollow)
}];
return YES;
}
The -registerDefaults: message establishes a backup dictionary for the user default’s primary dictionary.
The user defaults object actually manages several dictionaries, arranged into domains. When you ask it to
retrieve avalue, it searches each domain until it finds a value and returns it. The -registerDefaults: method sets up a domain behind all of the others, so if none of the other domains contain a value for kPreferenceHeading, thisdictionaryprovides one.
Now you can clean up the code in -viewDidLoad.
Return to HPViewController.m and replace the code you previously added with this (updated code in bold):
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
_mapView.mapType = [userDefaults integerForKey:kPreferenceMapType];
_mapView.userTrackingMode = [userDefaults integerForKey:kPreferenceHeading];
Isn’t that a lot simpler? Because you’ve registered a defaults dictionary, your code doesn’t have to worry
about the situation where there is no value for kPreferenceHeading, because now there will always be one.
Now that your map settings are persistent, it’s time to do something about that saved map location.
Turning Objects into Property List Objects
The big limitation of property lists is that they can only contain property list objects (NSNumber, NSString, NSDictionary, and so on). Anything you want to store in user defaults (or any property list) must be converted into one ofthose objects. Here are three most common techniques for storing other kinds of values:
n Convert the value(s) into a string
n Convert the values into a property list dictionary
n Serialize the object(s) into an NSData object
The first technique is simple enough, especially since there are a number of Cocoa Touch functions that will do this for you. For example, let’s say you need to store a CGRect value in your user defaults. CGRect isn’t a property listobject—it’s not even an object. You could store each of its four floating- point fields as separate values, like this:
CGRect saveRect = self.someView.frame;
[userDefaults setFloat:saveRect.origin.x forKey:@"HPFrame.x"]; [userDefaults setFloat:saveRect.origin.y forKey:@"HPFrame.y"]; [userDefaults setFloat:saveRect.size.height forKey:@"HPFrame.height"]; [userDefaultssetFloat:saveRect.size.width forKey:@"HPFrame.width"];
And you’d have to reverse the process to restore the rectangle. That seems like a lot of work. Fortunately, there are two functions—NSStringFromCGRect and CGRectFromString—that will convert a rectangle into a string objectand back again. Now the code to save your rectangle can look something like this:
[userDefaults setObject:NSStringFromCGRect(self.someView.frame) forKey:@"HPFrame"];
So if you can find functions that will convert your value to and from a property list object, use them.
The second technique is what you’re going to use for the map location. You’re going to write
a pair of methods. The first will return the salient properties of your MKPointAnnotation object as a dictionary of NSString and NSNumber objects. A second method will take that dictionary and set them again.
Start by adding a new category to your
project. Select
a file, like HPViewController.m, in
your project navigator and choose the New ➤ File...command (File menu or right/control+click). From the Cocoa Touch group, choosethe Objective-C category template.
Name the category HPPreservation and make
it a category on MKPointAnnotation.
In the @interface of MKPointAnnotation+HPPreservation.h, add two method declarations:
- (NSDictionary*)preserveState;
- (void)restoreState:(NSDictionary*)state;
In MKPointAnnotation+HPPreservation.m, define three constants for the dictionary keys, immediately after the #import statements:
#define kInfoLocationLatitude @"lat"
#define kInfoLocationLongitude @"long"
#define kInfoLocationTitle @"title"
In the @implementation section, write the two methods:
- (NSDictionary*)preserveState
{
CLLocationCoordinate2D coord = self.coordinate;
return @{ kInfoLocationLatitude: @(coord.latitude), kInfoLocationLongitude: @(coord.longitude), kInfoLocationTitle: self.title };
}
- (void)restoreState:(NSDictionary*)state
{
CLLocationCoordinate2D coord;
coord.latitude = [state[kInfoLocationLatitude] doubleValue]; coord.longitude = [state[kInfoLocationLongitude] doubleValue]; self.coordinate = coord;
self.title = state[kInfoLocationTitle];
}
The first method returns a new dictionary (NSDictionary) object with three values: the latitude, the longitude, and the title of the annotation. The values in the dictionary are NSNumber and NSString objects, all perfectly suited to beingstored in a property list. Which is exactly what you’re going to do.
The second method reverses the process, setting the coordinates and the title of the annotation using the values in the dictionary. Now let’s go use these to save and restore the map location.
Preserving and Restoring savedLocation
Return to HPViewController.m. You’re going to use the same technique you used to preserve and restore the map settings for the remembered map location. You’re going to save the location information (dictionary) when it’sestablished, and restore it when the app starts again. The savedLocation object isn’t, however, a simple integer, so the code is a little more involved.
Furthermore, you’re now establishing a new location from two places in the code: when the user sets
it and when the app starts again. As you know by now, I’m not fond of repeating code, so I’m going to have you consolidate the code that sets the location. This will come in handy later, when you add a third avenue for settingthe location.
To summarize, here’s what you’re going to change:
Add a -setLocation: method to set or clear the saved location
Write -preserveAnnotation and -restoreAnnotation methods to store, and retrieve, the map location from the user defaults
Add code to -dropPin: and -clearPin: to preserve the map location Restore any remembered location when your app launches
Begin by importing the category you just created, immediately after the other #import statements:
#import "MKPointAnnotation+HPPreservation.h"
Add the new method declarations to the private @interface HPViewController () section:
- (void)setAnnotation:(MKPointAnnotation*)annotation;
- (void)preserveAnnotation;
- (void)restoreAnnotation;
Add the new -setAnnotation: method to the @implementation section:
- (void)setAnnotation:(MKPointAnnotation*)annotation
{
if ([savedAnnotation isEqual:annotation]) return;
if (savedAnnotation!=nil)
[_mapView removeAnnotation:savedAnnotation]; savedAnnotation = annotation;
if (annotation!=nil)
{
[_mapView addAnnotation:annotation];
[_mapView selectAnnotation:annotation animated:YES];
}
}
This method will be used throughout MKViewController to set, or clear, the annotation object. It follows a common setter method pattern that handles the cases where the savedAnnotation variable is nil, the annotation parameter is nil, both are nil, or neither are nil. It also deliberately takes no action if the same annotation object is set again.
Find the -alertView:clickedButtonAtIndex: method. Locate the [self clearPin:self] statement. Delete it, along with all of the statements in the method that follow it, and replace them with the following code:
MKPointAnnotation *newAnnotation = [MKPointAnnotation new]; newAnnotation.title = name;
newAnnotation.coordinate = location.coordinate; [self setAnnotation:newAnnotation];
[self preserveAnnotation];
}
The new code makes two changes. First, it uses the new setAnnotation: method to add the annotation to
the map. Second, it sends the -preserveAnnotation message to store the new map location in the user defaults. Nowmake a similar change to the -clearPin: method (modified code in bold):
- (IBAction)clearPin:(id)sender
{
if (savedAnnotation!=nil)
{
[self setAnnotation:nil]; [self preserveAnnotation];
}
}
Add the new -preserveAnnotation and -restoreAnnotation methods:
- (void)preserveAnnotation
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; if (savedAnnotation!=nil)
{
NSDictionary *annotationInfo = [savedAnnotation preserveState]; [userDefaults setObject:annotationInfo
forKey:kPreferenceSavedLocation];
} else
{
[userDefaults removeObjectForKey:kPreferenceSavedLocation];
}
}
- (void)restoreAnnotation
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSDictionary *restoreInfo =
[userDefaults dictionaryForKey:kPreferenceSavedLocation]; if (restoreInfo!=nil)
{
MKPointAnnotation *restoreAnnotation = [MKPointAnnotation new]; [restoreAnnotation restoreState:restoreInfo];
[self setAnnotation:restoreAnnotation];
}
}
-preserveAnnotation converts the savedAnnotation object into a dictionary, suitable for storing in
the user defaults. If there is no map location, it intentionally deletes any saved value for that key from the user defaults. You can’t store nil as a value in user defaults. To store nothing, delete the value
by sending the -removeObjectForKey: message.
The restoreAnnotation method reverses the process, retrieving the dictionary of map location information fromuser defaults and turning it back into an MKPointAnnotation object with the same information. There’s only one thing left to do. In -viewDidLoad, add this statement to the end of the method:
[self restoreAnnotation];
Pigeon now has the memory of an elephant! Reuse the test procedure you employed earlier to test the map settings:
1. Run Pigeon
2. Remember a location on the map
3. Press the home button to put the app in the background
4. Stop the app in Xcode
5. Run the app again
When the app is restarted, the saved location is still there. Success!
This project demonstrates several common techniques for putting user defaults to work in your
app. Remembering user preferences, settings, and working data (like the saved map location) are all perfect uses for the user defaults.
Another common use is to save your app’s display state. When the user selects the Artists tab in the Music app, taps down into an album, and ultimately a song, they aren’t surprised when they start Music the next day andfind themselves at the same track, of the same album, of the same artist, in the Artists tab. That's because the Music app went to some effort to remember exactly what view controller the user left off at, and reconstructed itthe next time it was launched.
From what you know so far, you might think that you’d have to write code to capture the state of tab view and navigation view controllers, convert those into property list objects, store them in user defaults, and unroll the wholething again when the app restarts. That’s basically what happens,
but you’ll be happy to know that you don’t have to do (much) of that yourself. iOS has a specific mechanism for saving and restoring the state of your view controllers.
Persistent Views
In the section “Minimizing Updates and Code” I said the primary techniques for capturing user defaults was (a) when the value changes or (b) at a dependable exit point. You used technique
(a) in Pigeon because it was a perfect fit. The values you were saving were only changed in a handful of places, and they change infrequently. But that isn’t always the case.
Some changes occur constantly (like which view controller the user is in) and some changes occur
in a myriad of different ways, making it very difficult to catch them all. In these situations, the second approach is the best. You don’t worry about trying to monitor, or even care about, what changes are being made. Just arrangeto capture that value before the user quits the app, dismisses the view controller, or exits whatever interface they’re using. There are two exit points that make good places to capture changes:
n Dismissing a view controller
n The app entering the background
For view controllers, you can capture your values in the code that dismisses the view controller. You might have to do a little extra work in circumstances like a popover view controller, as tapping outside the popover candismiss it implicitly. You’d want to catch that message
(-popoverControllerDidDismissPopover:) too, so you don’t miss that exit route. But for the most part, it’s usually pretty easy to catch all of the ways a view controller can be dismissed.
Fading Into the Background
The other great place to capture changes, and particularly the view state, is when the app switches to the background. To appreciate this technique, you need to understand the states an iOS app progresses through. Your iOSapp is always in one of these states:
n Not running
n Foreground
n Background
n Suspended
Your app is in the “not running” state before it’s launched, or after it’s ultimately terminated. Very little happens when it’s not running.
The foreground state is the one you have the most experience with. This is when your app appears
in the device’s display and your user is interacting with it. Foreground has two sub-states, active and inactive, that it jumps between. Active means your app is running. Inactive occurs when something interrupts it (like a phonecall or an alert), but it’s still being displayed. Your app’s code does not run when it’s inactive. The inactive state usually doesn’t last long.
Your app moves to the background state when you press the home button, switch to another app,
or the screen locks. Your app continues to run for a short period of time, but will quickly move to the suspended state.
Your app does not execute any code once suspended. If iOS later decides that it needs the memory your app is occupying, or the user shuts down their device, your suspended app will terminate (without warning) and return tothe not running state.
But your app might not be terminated. If the user re launches your app, and it’s still in the background state, your app isn’t restarted; it’s simply activated again. It moves directly to the foreground state and
instantlyresumes execution. Your app may enter, and exit, the background state repeatedly over its lifetime.
Apps take advantage of this small window of background processing to prepare themselves for termination.
This is when the user defaults serializes its property values and saves them to persistent storage. It’s also the perfecttime to capture the state of your interface.
Your app can discover when it has entered the background state in two ways. Your app delegate object receives an -applicationDidEnterBackground: message. Around the same time, aUIApplicationDidEnterBackgroundNotification notification is posted. Override that method, or have any object observe that notification, and save whatever state information you need.
iOS also provides a mechanism to capture, and later restore, the state of your view controllers.
This is automatically invoked when your app enters the background state.
Preserving View Controllers
As an example, take the Wonderland app. (I mean that, literally. Go find the finished Wonderland app from
Chapter 12. You’re going to modify it.) The user can spend all day jumping between tabs,browsing characters in thetable view, and flipping through the page view. You want to catch the point when the app switches to the background and remember what tab they had active and what page of the book they were looking at. You’ll use this torestore those views the next time the app is launched.
When an iOS app enters the background, iOS examines the active view controller.
If properly configured, it will automatically preserve its state in the user defaults. This is a combination of
what iOS already knows about theview controller and additional information that your code supplies.
Specifically, iOS will remember what tab view was being displayed, the scroll position in a table view, and so on. To that, you can add custom information thatonly your app understands. For Wonderland, you’re going to remember the page number the user was reading. (Remember that a
page view controller has no concept of a page number; that’s something you invented for your page view controller data source.)
The first thing to address is the “properly configured” prerequisite. To put iOS to work for you, preserving and restoring your view controllers, you must do two things:
1. Implement the -application:shouldSaveApplicationState: and -applicatio n:shouldRestoreApplicationState: app delegate methods
2. Assign restoration identifiers to your view controllers, starting with the root view controller
The first step tells iOS that you want its help in preserving and restoring your app’s view state. These
methods must be implemented, and they must return YES, or iOS will just pass your app by. They also
serve asecondary function. If you have any custom, app wide, state information that you want to
preserve, these are the methods to do that in. Wonderland doesn’t have any, so it only needs to return YES.
Open the Wonderland project from Chapter 12 and select the WLAppDelegate.m file. Add the following two methods to the @implementation section:
- (BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
return YES;
}
- (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;
}

No comments:
Post a Comment