Wednesday, 7 May 2014

Property Lists [Remember Me?]

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, thats 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 dont 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 apps 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, isnt a property list object
and cant 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 wont be confused with any keys iOS might be using. A number of iOS frameworks also use your apps user defaults to preserve information. The simplest technique is to use the same class prefix that your project uses. For example, its 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 wont 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. Its something to avoid, as long as it doesnt 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) its 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 users 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. Thats 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.

Thats 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 theres 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 NO0, or 0.0. Here are three ways of dealing with this situation:

n     Choose your values so that nilNO0, 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 isnt so lucky. The initial tracking mode Pigeon uses is MKUserTrackingModeFollow,
whose integer value is 1. If theres no value for the kPreferenceHeading key, you dont 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 theres 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.

Thats everything you need to preserve and restore these map settings. Its 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. Thats 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 ouse the Hardware  Home command in the simulator, shown in the third image in Figure 18-1. When your app entersthe background, it doesnt 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 users saved map location.


Registering Default Values

The code to restore the tracking mode is awfully uglyWell, maybe not “awfully ugly,” but its a little ugly. If you had a dozen of these settings to restore, you’d have a lot of repetitive code to write. Fortunately, theres 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 apps 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 defaults 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];

Isnt that a lot simpler? Because you’ve registered a defaults dictionary, your code doesnt 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, its 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 (NSNumberNSString, 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, lets say you need to store a CGRect value in your user defaults. CGRect isnt a property listobject—its 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, thlongitude, 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 lets 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 itsestablished, and restore it when the app starts again. The savedLocation object isnt, 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, heres 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 cant 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 locatioinformation fromuser defaults and turning it back into an MKPointAnnotatio object with thsame information. Thereonly 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 apps display state. When the user selects the Artists tab in the Music app, taps down into an album, and ultimately a song, they arent 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. Thats basically what happens,
but you’ll be happy to know that you dont 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 isnt 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 dont 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 controllerYou 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 dont miss that exit route. But for the most part, its 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 its launched, or after its ultimately terminated. Very little happens when its not running.

The foreground state is the one you have the most experience with. This is when your app appears
in the devices 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 its still being displayed. Your apps code does not run when its inactive. The inactive state usually doesnt 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 its still in the background state, your app isnt restarted; its 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. Its 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 viewYou 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; thats 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 apps view state. These 
methods must be implemented, and they must return YES, or iOS will just pass your apby. They also
serve asecondary functionIf you have ancustom, app wide, state information that you want to
preserve, these are the methods to do that in. Wonderland doesnt 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