27
iOS Programming iOS Programming: The Big Nerd Ranch Guide by Joe Conway and Aaron Hillegass Copyright © 2012 Big Nerd Ranch, Inc. All rights reserved. Printed in the United States of America. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. For information regarding permissions, contact Big Nerd Ranch, Inc. 1989 College Ave. Atlanta, GA 30317 (404) 478-9005 http://www.bignerdranch.com/ [email protected] The 10-gallon hat with propeller logo is a trademark of Big Nerd Ranch, Inc. Exclusive worldwide distribution of the English edition of this book by Pearson Technology Group 800 East 96th Street Indianapolis, IN 46240 USA http://www.informit.com The authors and publisher have taken care in writing and printing this book but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein. App Store, Apple, Cocoa, Cocoa Touch, Finder, Instruments, iCloud, iPad, iPhone, iPod, iPod touch, iTunes, Keychain, Mac, Mac OS, Multi-Touch, Objective-C, OS X, Quartz, Retina, Safari, and Xcode are trademarks of Apple, Inc., registered in the U.S. and other countries. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals. ISBN-10 0321821521 ISBN-13 978-0321821522 Third edition, second printing, August 2012

iOS Programming: The Big Nerd Ranch Guide by Joe Conway and Aaron Hillegass

Embed Size (px)

DESCRIPTION

iOS Programming: The Big Nerd Ranch Guideby Joe Conway and Aaron Hillegass

Citation preview

Page 1: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

iOS Programming

iOS Programming: The Big Nerd Ranch Guideby Joe Conway and Aaron Hillegass

Copyright © 2012 Big Nerd Ranch, Inc.

All rights reserved. Printed in the United States of America. This publication is protected by copyright, andpermission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system,or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. Forinformation regarding permissions, contact

Big Nerd Ranch, Inc.1989 College Ave.Atlanta, GA 30317(404) 478-9005http://www.bignerdranch.com/[email protected]

The 10-gallon hat with propeller logo is a trademark of Big Nerd Ranch, Inc.

Exclusive worldwide distribution of the English edition of this book by

Pearson Technology Group800 East 96th StreetIndianapolis, IN 46240 USAhttp://www.informit.com

The authors and publisher have taken care in writing and printing this book but make no expressed or impliedwarranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidentalor consequential damages in connection with or arising out of the use of the information or programs containedherein.

App Store, Apple, Cocoa, Cocoa Touch, Finder, Instruments, iCloud, iPad, iPhone, iPod, iPod touch, iTunes,Keychain, Mac, Mac OS, Multi-Touch, Objective-C, OS X, Quartz, Retina, Safari, and Xcode are trademarks ofApple, Inc., registered in the U.S. and other countries.

Many of the designations used by manufacturers and sellers to distinguish their products are claimed astrademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, thedesignations have been printed with initial capital letters or in all capitals.

 ISBN-10  0321821521ISBN-13  978-0321821522

Third edition, second printing, August 2012

sloper
Highlight
sloper
Highlight
Page 2: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Creating an Xcode Project

3

On the next pane, enter Quiz for the Product�Name and com.bignerdranch as the Company�Identifier.(Or replace bignerdranch with your company name). Enter Quiz in the Class�Prefix field, and fromthe pop-up menu labeled Device�Family, select iPhone. We only want the box labeled Use�AutomaticReference�Counting checked, so uncheck the others. Once your screen looks like Figure 1.3, pressNext.

Figure 1.3  Naming a new project

We chose iPhone for this application’s device family, but Quiz will run on the iPad, too. It will run inan iPhone-sized window that does not make the most of the iPad screen, but that’s okay for now. Forthe applications in the first part of this book, we will stick with the iPhone device family template.In these chapters, you’ll be focused on learning the fundamentals of the iOS SDK, and these are thesame across devices. Later, we will look at some iPad-only options and how to make applications runnatively on both iOS device families.

Now another sheet will appear asking you to save the project. Save the project in the directory whereyou plan to store all of the exercises in this book. You can uncheck the box that creates a local gitrepository, but keeping it checked doesn’t hurt anything.

Once the project is created, it will open in the Xcode workspace window (Figure 1.4).

sloper
Highlight
Page 3: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Implementing Methods

19

Figure 1.19  Example of code-completion placeholder and errors

See the nibNameOrNil and nibBundleOrNil in the first line of the implementation ofinitWithNibName:bundle:? Those are placeholders. You can tell because they are inside slightly-shaded, rounded rectangles. The fix is to delete the placeholders and type in arguments of your own(with the same names). The rounded rectangles will go away, and your code will be correct and valid.

Second, don’t blindly accept the first suggestion Xcode gives you without verifying it. Cocoa Touchuses naming conventions, which often cause distinct methods, types, and variables to have very similarnames. Many times, the code-completion will suggest something that looks an awful lot like what youwant, but it is not the code you are looking for. Always double-check.

Now back to your code. In the declarations in QuizViewController.h, neither questions or answersis labeled IBOutlet. This is because the objects that questions and answers point to are created andconfigured programmatically in the code above instead of in the XIB file. This is a standard practice:view objects are typically created in XIB files, and model objects are always created programmatically.

In addition to the initWithNibName:bundle: method, we need two action methods for when thebuttons are tapped. In QuizViewController.m, add the following code after the implementation ofinitWithNibName:bundle:. Make sure this code is before the @end directive but not inside the curlybrackets of the initWithNibName:bundle: implementation.

sloper
Highlight
Page 4: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Other initializers and the initializer chain

51

Other initializers and the initializer chainA class can have more than one initializer. First, let’s consider a hypothetical example. BNRItem couldhave an initializer that takes only an NSString for the itemName. Its declaration would look like this:

- (id)initWithItemName:(NSString *)name;

In this initializer’s definition, you wouldn’t replicate the code in the designated initializer. Instead,this initializer would simply call the designated initializer, pass the information it was given as theitemName, and pass default values for the other arguments.

- (id)initWithItemName:(NSString *)name{ return [self initWithItemName:name valueInDollars:0 serialNumber:@""];}

Using initializers as a chain like this reduces the chance for error and makes maintaining code easier.For classes that have more than one initializer, the programmer who created the class chooses whichinitializer is designated. You only write the core of the initializer once in the designated initializer, andother initialization methods simply call that core with default values.

Now let’s look at a real example. BNRItem actually has another initializer, init, which it inherits fromits superclass NSObject. If init is sent to an instance of BNRItem, none of the stuff you put in thedesignated initializer will be called. Therefore, you must link BNRItem’s implementation of init to itsdesignated initializer.

In BNRItem.m, override the init method to call the designated initializer with default values for all ofthe arguments.

- (id)init{ return [self initWithItemName:@"Item" valueInDollars:0 serialNumber:@""];}

The relationships between BNRItem’s initializers (real and hypothetical) are shown in Figure 2.14; thedesignated initializers are white, and the additional initializer is gray.

Figure 2.14  Initializer chain

sloper
Highlight
Page 5: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 2  Objective-C

52

Let’s form some simple rules for initializers from these ideas.

• A class inherits all initializers from its superclass and can add as many as it wants for its ownpurposes.

• Each class picks one initializer as its designated initializer.

• The designated initializer calls the superclass’s designated initializer.

• Any other initializer a class has calls the class’s designated initializer.

• If a class declares a designated initializer that is different from its superclass, the superclass’sdesignated initializer must be overridden to call the new designated initializer.

Using Initializers

Currently, the code in main.m sends the message init to the new instance of BNRItem. With these newinitializer methods, this message will run the init method you just implemented in BNRItem, whichcalls the designated initializer (initWithItemName:valueInDollars:serialNumber:) and passesdefault values. Let’s make sure this works as intended.

In main.m, log the BNRItem to the console after it is initialized but before the setter messages are sent.

BNRItem *p = [[BNRItem alloc] init];

NSLog(@"%@", p);

// This creates a new NSString, "Red Sofa", and gives it to the BNRItem[p setItemName:@"Red Sofa"];

Build and run the application. Notice that the console spits out the following messages:

Item (): Worth $0, recorded on 2011-07-19 18:56:42 +0000Red Sofa (A1B2C): Worth $100, recorded on 2011-07-19 18:56:42 +0000

Now replace the code that initializes the BNRItem and the code sets its instance variables with asingle message send using the designated initializer. Also, get rid of the code that populates theNSMutableArray with strings and prints them to the console. In main.m, make the following changes:

#import <Foundation/Foundation.h>#import "BNRItem.h"

int main (int argc, const char * argv[]){ @autoreleasepool {

NSMutableArray *items = [[NSMutableArray alloc] init]; [items addObject:@"One"]; [items addObject:@"Two"]; [items addObject:@"Three"]; [items insertObject:@"Zero" atIndex:0];

for (int i = 0; i < [items count]; i++) { NSLog(@"%@", [items objectAtIndex:i]); }

sloper
Highlight
Page 6: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Declaring properties

73

The first attribute of a property has two options: nonatomic or atomic. This attribute has to do withmulti-threaded applications and is outside the scope of this book. Most Objective-C programmerstypically use nonatomic: we do at Big Nerd Ranch, and so does Apple. In this book, we’ll usenonatomic for all properties.

Let’s change BNRItem to use properties instead of accessor methods. In BNRItem.h, replace all of youraccessor methods with properties that are nonatomic.

- (id)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;

- (void)setItemName:(NSString *)str;- (NSString *)itemName;

- (void)setSerialNumber:(NSString *)str;- (NSString *)serialNumber;

- (void)setValueInDollars:(int)i;- (int)valueInDollars;

- (NSDate *)dateCreated;

- (void)setContainedItem:(BNRItem *)i;- (BNRItem *)containedItem;

- (void)setContainer:(BNRItem *)i;- (BNRItem *)container;

@property (nonatomic) BNRItem *containedItem;@property (nonatomic) BNRItem *container;

@property (nonatomic) NSString *itemName;@property (nonatomic) NSString *serialNumber;@property (nonatomic) int valueInDollars;@property (nonatomic) NSDate *dateCreated;

@end

Unfortunately, nonatomic is not the default option, so you will always need to explicitly declare yourproperties to be nonatomic.

The second attribute of a property is either readwrite or readonly. A readwrite property declaresboth a setter and getter, and a readonly property just declares a getter. The default option for thisattribute is readwrite. This is what we want for all of BNRItem’s properties with the exception ofdateCreated, which should be readonly. In BNRItem.h, declare dateCreated as a readonly propertyso that no setter method is declared for this instance variable.

@property (nonatomic, readonly) NSDate *dateCreated;

The final attribute of a property describes its memory management. The default option depends onthe type of the property. A property whose type is not a pointer to an object, like int, does not needmemory management and thus defaults to assign. BNRItem only has one property that is not a pointerto an object, valueInDollars. For pointers to objects, like NSString *, this attribute defaults tostrong. BNRItem has five object pointer properties: four of these will use strong, and the containerproperty will use weak to avoid a retain cycle. With pointers to objects, it is good to be explicit and usethe strong property to avoid confusion. In BNRItem.h, update the property declarations as shown.

sloper
Highlight
Page 7: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Diagnosing crashes and exceptions

95

Diagnosing crashes and exceptionsWhile you can set breakpoints yourself to break on a particular line, it would be nice if the debuggerwould automatically set a breakpoint on any line that causes your application to crash or that causes anexception to be thrown.

To get the debugger to do this, we need to add a breakpoint for all exceptions. Select the breakpointnavigator. Then, at the bottom of the navigator area, click the + icon and select Add�ExceptionBreakpoint.... Then, click the Done button on the panel that pops up (Figure 4.13).

Figure 4.13  Turning on exception breakpoints

Now let’s introduce an exception in Whereami. In WhereamiViewController.m, delete the entireimplementation of doSomethingWeird:

- (void)doSomethingWeird{ NSLog(@"Line 1"); NSLog(@"Line 2"); NSLog(@"Line 3");}

But don’t delete the line in initWithNibName:bundle: that sends the doSomethingWeird message.

Now WhereamiViewController no longer implements doSomethingWeird, so when it sends thismessage to self, an unrecognized selector exception will be thrown. Build and run the application.

Immediately after launch, the application will blow up. The debugger will show you where (look forthe green execution indicator), and the console will show you why. Notice that the stack trace is a bit

sloper
Highlight
Page 8: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Your own MKAnnotation

115

Figure 5.13  MKMapView and its annotations

Now you’re going to write a new class called BNRMapPoint that will conform to MKAnnotation. Whenthe user tags a location, an instance of BNRMapPoint will be created and represented on the map.

From the File menu, select New and then New�File.... Then from the iOS section, choose Cocoa�Touch,select Objective-C�class, and click Next (Figure 5.14).

Figure 5.14  Creating an NSObject subclass

On the next pane, enter BNRMapPoint, select NSObject from the superclass list, and hit Next.

sloper
Highlight
Page 9: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Subclassing UIViewController

147

Figure 7.2  Creating HypnosisViewController

Open HypnosisViewController.h and change the superclass to UIViewController.

@interface HypnosisViewController : NSObject@interface HypnosisViewController : UIViewController

@end

A view controller is responsible for creating its view hierarchy. HypnosisViewController’s viewhierarchy will be made up of only one view, an instance of HypnosisView – the UIView subclass youcreated in Chapter 6. Locate HypnosisView.h and HypnosisView.m in Finder and drag them intoHypnoTime’s project navigator.

In the sheet that appears, check the box to Copy�items�into�destination�group’s�folder and the box toadd these files to the target that is HypnoTime (Figure 7.3). Then click Finish. This will create a copyof the two files, add those files to HypnoTime’s directory on the filesystem, and then add them to theHypnoTime project.

sloper
Highlight
Page 10: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

173

8Notification and Rotation

Objective-C code is all about objects sending messages to other objects. This communication usuallyoccurs between two objects, but sometimes a bunch of objects are concerned with one object. They allwant to know when this object does something interesting, and it’s not feasible for that object to sendmessages to every interested object.

Instead, an object can post notifications about what it is doing to a centralized notification center.Interested objects register to receive a message when a particular notification is posted or whena particular object posts. In this chapter, you will learn how to use a notification center to handlenotifications. You’ll also learn about the autorotation behavior of UIViewController.

Notification CenterEvery application has an instance of NSNotificationCenter, which works like a smart bulletin board.An object can register as an observer (“Send me ‘lost dog’ notifications”). When another object postsa notification (“I lost my dog”), the notification center forwards the notification to the registeredobservers (Figure 8.1).

Figure 8.1  NSNotificationCenter

Notifications are instances of NSNotification. Every NSNotification object has a name and a pointerback to the object that posted it. When you register as an observer, you can specify a notification name,a posting object, and the message you want sent to you when a qualifying notification is posted.

sloper
Highlight
Page 11: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 8  Notification and Rotation

174

The following snippet of code registers you for notifications named LostDog that have been posted byany object. When an object posts a LostDog notification, you’ll be sent the message retrieveDog:.

NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];[nc addObserver:self // The object self will be sent selector:@selector(retrieveDog:) // retrieveDog: name:@"LostDog" // when @"LostDog" is posted object:nil]; // by any object.

Note that nil works as a wildcard in the notification center world. You can pass nil as the nameargument, which will give you every notification regardless of its name. If you pass nil for thenotification name and the posting object, you will get every notification.

The method that is triggered when the notification arrives takes an NSNotification object as theargument:

- (void)retrieveDog:(NSNotification *)note{ id poster = [note object]; NSString *name = [note name]; NSDictionary *extraInformation = [note userInfo];}

Notice that the notification object may have a userInfo dictionary attached to it. This dictionary isused to pass additional information, like a description of the dog that was found. Here’s an example ofan object posting a notification with a userInfo dictionary attached:

NSDictionary *extraInfo = [NSDictionary dictionaryWithObject:@"Fido" forKey:@"Name"];NSNotification *note = [NSNotification notificationWithName:@"LostDog" object:self userInfo:extraInfo];[[NSNotificationCenter defaultCenter] postNotification:note];

For a (real-world) example, when a keyboard is coming onto the screen, it posts aUIKeyboardDidShowNotification that has a userInfo dictionary. This dictionary contains the on-screen region that the newly visible keyboard occupies.

This is important: the notification center does not keep strong references to its observers. If the objectdoesn’t remove itself as an observer before it is destroyed, then the next time a notification that theobject registered for is posted, the center will try to send the object a message. Since that object nolonger exists, your application will crash. Thus, if an object registers with the notification center, thatobject must unregister in its dealloc method.

- (void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self];}

It’s important to understand that NSNotifications and the NSNotificationCenter are not associatedwith visual “notifications,”, like push and local notifications that the user sees when an alarm goes offor a text message is received. NSNotifications and the NSNotificationCenter comprise a designpattern, like target-action pairs or delegation.

UIDevice NotificationsOne object that regularly posts notifications is UIDevice. Here are the constants that serve as names ofthe notifications that a UIDevice posts:

sloper
Highlight
Page 12: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 9  UITableView and UITableViewController

194

Thus, a knowledgeable programmer could still create an instance of BNRItemStore viaallocWithZone:, which would bypass our sneaky alloc trap. To prevent this possibility, overrideallocWithZone: in BNRItemStore.m to return the single BNRItemStore instance.

+ (id)allocWithZone:(NSZone *)zone{ return [self sharedStore];}

Now if sharedStore were to send alloc or allocWithZone: to BNRItemStore, then the method wouldcall BNRItemStore’s implementation of allocWithZone:. That implementation just calls sharedStore,which would then call BNRItemStore’s allocWithZone: again, which would then call sharedStore,which would... well, you get the picture.

Figure 9.8  Not sending allocWithZone: to NSObject causes loop

This is why we had sharedStore call NSObject’s implementation of allocWithZone:.

sharedStore = [[super allocWithZone:nil] init];

By sending allocWithZone: to super, we skip over our trap and get an instance of BNRItemStorewhen we need it (Figure 9.9).

Figure 9.9  BNRItemStore and NSObject allocation methods

We can only skip over our alloc trap within the implementation of BNRItemStore because the superkeyword is only relevant to the class in which the method is implemented.

Chris
New Stamp
Chris
New Stamp
Chris
New Stamp
Chris
New Stamp
Page 13: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 11  UINavigationController

220

UINavigationControllerWhen your application presents multiple screens of information, UINavigationController maintainsa stack of those screens. Each screen is the view of a UIViewController, and the stack is an array ofview controllers. When a UIViewController is on top of the stack, its view is visible.

When you initialize an instance of UINavigationController, you give it one UIViewController.This UIViewController is the navigation controller’s root view controller. The root viewcontroller is always on the bottom of the stack. More view controllers can be pushed on top of theUINavigationController’s stack while the application is running. This ability to add to the stackat runtime is missing from UITabBarController, which gets all of its view controllers when it isinitialized. With a navigation controller, only the root view controller is guaranteed to always be in thestack.

When a UIViewController is pushed onto the stack, its view slides onto the screen from the right.When the stack is popped, the top view controller is removed from the stack, and the view of the onebelow it slides onto the screen from the left.

Figure 11.2 shows a navigation controller with two view controllers: a root view controller and anadditional view controller above it at the top of the stack. The view of the additional view controller iswhat the user sees because that view controller is at the top of the stack.

Figure 11.2  UINavigationController’s stack

Like UITabBarController, UINavigationController has a viewControllers array. The root viewcontroller is the first object in the array. As more view controllers are pushed onto the stack, theyare added to the end of this array. Thus, the last view controller in the array is the top of the stack.UINavigationController’s topViewController property keeps a pointer to the top of the stack.

UINavigationController is a subclass of UIViewController, so it has a view of its own. Its viewalways has two subviews: a UINavigationBar and the view of topViewController (Figure 11.3). Youcan install a navigation controller as the rootViewController controller of the window to insert itsview as a subview of the window.

sloper
Highlight
Page 14: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 13  UIPopoverController and Modal View Controllers

264

A popover controller displays another view controller’s view in a bordered window that floats abovethe rest of the application’s interface. When you create a UIPopoverController, you set this otherview controller as the popover controller’s contentViewController.

In this chapter, you will present the UIImagePickerController in a UIPopoverController when theuser taps the camera bar button item in the DetailViewController’s view.

Figure 13.3  UIPopoverController

In DetailViewController.h, add an instance variable to hold the popover controller. Also, declarethat DetailViewController conforms to the UIPopoverControllerDelegate protocol.

@interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate, UIPopoverControllerDelegate>{ __weak IBOutlet UITextField *nameField; __weak IBOutlet UITextField *serialNumberField; __weak IBOutlet UITextField *valueField; __weak IBOutlet UILabel *dateLabel; __weak IBOutlet UIImageView *imageView;

UIPopoverController *imagePickerPopover;}

In DetailViewController.m, add the following code to the end of takePicture:.

[imagePicker setDelegate:self];

[self presentViewController:imagePicker animated:YES completion:nil];

// Place image picker on the screen

sloper
Highlight
Page 15: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 13  UIPopoverController and Modal View Controllers

266

When you explicitly send the message dismissPopoverAnimated: to dismiss the popovercontroller, it does not send popoverControllerDidDismissPopover: to its delegate, so you must setimagePickerPopover to nil in dismissPopoverAnimated:.

There is a small bug to fix. If the UIPopoverController is visible and the user taps on the camerabutton again, the application will crash. This crash occurs because the UIPopoverController that is onthe screen is destroyed when imagePickerPopover is set to point at the new UIPopoverController intakePicture:. You can ensure that the destroyed UIPopoverController is not visible and cannot betapped by adding the following code to the top of takePicture: in DetailViewController.m.

- (IBAction)takePicture:(id)sender{ if ([imagePickerPopover isPopoverVisible]) { // If the popover is already up, get rid of it [imagePickerPopover dismissPopoverAnimated:YES]; imagePickerPopover = nil; return; }

UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];

Build and run the application. Tap the camera button to show the popover and then tap it again – thepopover will disappear.

More Modal View ControllersIn this part of the chapter, you will update Homepwner to present the DetailViewControllermodally when the user creates a new BNRItem. When the user selects an existing BNRItem, theDetailViewController will be pushed onto the UINavigationController’s stack as before.

Figure 13.4  New item

sloper
Highlight
Page 16: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 13  UIPopoverController and Modal View Controllers

274

In ItemsViewController.m, update the addNewItem: method to use a different transition.

[navController setModalPresentationStyle:UIModalPresentationFormSheet];[navController setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];

[self presentViewController:navController animated:YES completion:nil];

Build and run the application and notice the change in animation. Try out some of the other options,but make sure to read the fine print in the documentation. For instance, you can’t use the page curltransition unless the presentation style is full screen. Also, note that these transitions will still work ifyou switch back to deploying on an iPhone. The presentation style, however, will always be full screen.

Bronze Challenge: Universalizing WhereamiGo back to Chapter 5 and universalize Whereami. Make sure its interface appears just right on both theiPad and iPhone.

Silver Challenge: Peeling Away the LayersHave the DetailViewController be presented with the UIModalTransitionStylePartialCurl stylewhen creating a new item.

Gold Challenge: Popover AppearanceYou can change the appearance of a UIPopoverController. Do this for the popover that presentsthe UIImagePickerController. (Hint: check out the popoverBackgroundViewClass property inUIPopoverController.)

For the More Curious: View ControllerRelationshipsThe relationships between view controllers are important for understanding where and how a viewcontroller’s view appears on the screen. Overall, there are two different types of relationships betweenview controllers: parent-child relationships and presenting-presenter relationships. Let’s look at eachone individually.

Parent-child relationships

Parent-child relationships are formed when using view controller containers. Examplesof view controller containers are UINavigationController, UITabBarController, andUISplitViewController (which you will see in Chapter 26). You can identify a view controllercontainer because it has a viewControllers property that is an array of the view controllers it contains.

A view controller container is always a subclass of UIViewController and thus has a view.The behavior of a view controller container is that it adds the views of its viewControllersas subviews of its own view. A container has its own built-in interface, too. For example, aUINavigationController’s view shows a navigation bar and the view of its topViewController.

sloper
Highlight
Page 17: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 14  Saving, Loading, and Application States

292

because the DetailViewController was popped off the stack or a new image was taken), then it isdestroyed. It will be reloaded later if needed.

Model-View-Controller-Store Design PatternIn this exercise, we expanded on the BNRItemStore to allow it to save and load BNRItem instancesfrom the filesystem. The controller object asks the BNRItemStore for the model objects it needs, butit doesn’t have to worry about where those objects actually came from. As far as the controller isconcerned, if it wants an object, it will get one; the BNRItemStore is responsible for making sure thathappens.

The standard Model-View-Controller design pattern calls for the controller to bear the burden of savingand loading model objects. However, in practice, this can become overwhelming – the controller issimply too busy handling the interactions between model and view objects to deal with the detailsof how objects are fetched and saved. Therefore, it is useful to move the logic that deals with wheremodel objects come from and where they are saved to into another type of object: a store.

A store exposes a number of methods that allow a controller object to fetch and save model objects.The details of where these model objects come from or how they get there is left to the store. In thischapter, the store worked with a simple file. However, the store could also access a database, talk to aweb service, or use some other method to produce the model objects for the controller.

One benefit of this approach, besides simplified controller classes, is that you can swap out how thestore works without modifying the controller or the rest of your application. This can be a simplechange, like the directory structure of the data, or a much larger change, like the format of the data.Thus, if an application has more than one controller object that needs to save and load data, you onlyhave to change the store object.

You can also apply the idea of a store to objects like CLLocationManager. The location manager isa store that returns model objects of type CLLocation. The basic idea still stands: a model object isreturned to the controller, and the controller doesn’t care where it came from.

Thus, we introduce a new design pattern called Model-View-Controller-Store, or simply MVCS. It’sthe hip, new design pattern that programmers are talking about everywhere. This design pattern will beexpanded on in Chapter 28.

Bronze Challenge: PNGInstead of saving each image as a JPEG, save it as a PNG instead.

Silver Challenge: Archiving WhereamiAnother application you wrote could benefit from archiving: Whereami. In Whereami, archive theMapPoint objects so that they can be reused. (Hint: You cannot archive structures. However, you canbreak up structures into their primitive types....)

For The More Curious: Application StateTransitionsLet’s write some quick code to get a better understanding of the different application state transitions.

sloper
Highlight
Page 18: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 14  Saving, Loading, and Application States

296

<string>Hillegass</string> </dict></array></plist>

XML property lists are a convenient way to store data because they can be read on nearly any system.Many web service applications use property lists as input and output. The code for writing and readinga property list looks like this:

NSMutableDictionary *d = [NSMutableDictionary dictionary];[d setObject:@"A string" forKey:@"String"];[d writeToFile:@"/some/path/file" atomically:YES];

NSMutableDictionary *anotherD = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/some/path/file"];

For the More Curious: The Application BundleWhen you build an iOS application project in Xcode, you create an application bundle. The applicationbundle contains the application executable and any resources you have bundled with your application.Resources are things like XIB files, images, audio files – any files that will be used at runtime. Whenyou add a resource file to a project, Xcode is smart enough to realize that it should be bundled withyour application and categorizes it accordingly.

How can you tell which files are being bundled with your application? Select the Homepwner projectfrom the project navigator. Check out the Build�Phases pane in the Homepwner target. Everythingunder Copy�Bundle�Resources will be added to the application bundle when it is built.

Each item in the Homepwner target group is one of the phases that occurs when you build a project.The Copy�Bundle�Resources phase is where all of the resources in your project get copied into theapplication bundle.

You can check out what an application bundle looks like on the filesystem after you install anapplication on the simulator. Navigate to ~/Library/Application Support/iPhone Simulator/(version number)/Applications. The directories within this directory are the application sandboxesfor applications installed on your computer’s iOS simulator. Opening one of these directories willshow you what you expect in an application sandbox: an application bundle and the Documents, tmp,and Library directories. Right or Command-click the application bundle and choose Show�PackageContents from the contextual menu.

sloper
Highlight
Page 19: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 15  Subclassing UITableViewCell

310

[[cell nameLabel] setText:[p itemName]];

Why do we need to give the cell a pointer to the table view when the controller already has a pointer tothat table view? Wouldn’t it be simpler just to have the cell pass itself and let the controller determinethe table view? Yes, it would. But we’re doing it this way so that, later in the chapter, the cell can passits own index path to enable some cool Objective-C techniques. And serious bonus points for havingthese questions.

Relaying the message to the controller

Every cell will now know its controller and the table view it is displayed on. When the messageshowImage: is sent to a HomepwnerItemCell, we want the HomepwnerItemCell to turn around and tellthe ItemsViewController to show the image for the item at the cell’s index path.

In HomepwnerItemCell.m, implement showImage: to get its index path from the table view andthen send its controller the showImage:atIndexPath: message. (We’ll get to the implementation ofshowImage:atIndexPath: in a moment.)

- (IBAction)showImage:(id)sender{ NSIndexPath *indexPath = [[self tableView] indexPathForCell:self];

[[self controller] showImage:sender atIndexPath:indexPath];}

Now, whenever the button overlaid on the thumbnail is tapped, ItemsViewController will besent showImage:atIndexPath:. The first argument is a pointer to the button that sent the originalshowImage: message, and the second argument is the NSIndexPath of the cell that this button ison. At least that is what should happen. Unfortunately, HomepwnerItemCell doesn’t know thedetails of ItemsViewController. Thus, it doesn’t know that ItemsViewController implementsshowImage:atIndexPath:, and this code will generate an error.

One approach would be to import ItemsViewController.h into HomepwnerItemCell.m.However, this creates a dependency between HomepwnerItemCell and ItemsViewController andHomepwnerItemCell could not be used with any other view controller. Let’s take a different approachthat will keep the two classes separate and give us some flexibility down the road.

Objective-C selector magic

We can use the flexible power of Objective-C’s runtime messaging to generalize the implementationof showImage:. Instead of explicitly sending showImage:atIndexPath: to the controller whenshowImage: is called, we are going to send the message performSelector:withObject:withObject:.This method takes a SEL that is the selector of the message to be sent, and the next two arguments arethe arguments to be passed in that message (in order). The controller then searches for the methodwhose name matches the passed-in selector and executes it.

This is an interesting way of doing things, but we’ll see why it is useful in a moment. First, rememberthat a selector is just the name of a message. You can turn a selector into a string, and you can turna string back into a selector. In between these two steps, you can modify the string that becomes theselector. This is what you will do for HomepwnerItemCell – when it is sent the message showImage:,it will get that selector, append atIndexPath: to it, and send the new selector to its controller in

sloper
Highlight
Page 20: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 16  Core Data

332

Figure 16.12  Interface for BNRAssetType

Create a new Objective-C�class template file and choose NSObject as the superclass. Name this classAssetTypePicker.

In AssetTypePicker.h, forward declare BNRItem, change the superclass to UITableViewController,and give it a BNRItem property.

#import <UIKit/UIKit.h>

@class BNRItem;

@interface AssetTypePicker : NSObject@interface AssetTypePicker : UITableViewController

@property (nonatomic, strong) BNRItem *item;

@end

This table view controller will show a list of the available BNRAssetTypes. Tapping a button on theDetailViewController’s view will display it. Implement the data source methods and import theappropriate header files in AssetTypePicker.m. (You’ve seen all this stuff before.)

#import "AssetTypePicker.h"#import "BNRItemStore.h"#import "BNRItem.h"

@implementation AssetTypePicker

@synthesize item;

- (id)init{ return [super initWithStyle:UITableViewStyleGrouped];

sloper
Highlight
Page 21: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Localizing Resources

345

/* Class = "IBUILabel"; text = "Label"; ObjectID = "7"; */"7.text" = "Label";

Notice that we do not change the Label text because it will be created at runtime. Save this file.

Now you will use ibtool to create a new Spanish XIB file. This file will be based on the Englishversion of DetailViewController.xib but will replace all of the strings with the values fromDetailViewController.strings. To pull this off, you need to know the path of your English XIBfile and the path of your Spanish directory in this project’s directory. Remember, you opened thesewindows in Finder earlier.

In Terminal.app, enter the following command, followed by a space after write. (But don't hit returnyet!)

ibtool --import-strings-file ~/Desktop/DetailViewController.strings --write

Next, find DetailViewController.xib in es.lproj and drag it onto the terminal window. Then,find DetailViewController.xib in the en.lproj folder and drag it onto the terminal window. Yourcommand should look similar to this:

ibtool --import-strings-file ~/Desktop/DetailViewController.strings --write /iphone/Homepwner/Homepwner/es.lproj/DetailViewController.xib /iphone/Homepwner/Homepwner/en.lproj/DetailViewController.xib

This command says, “Create DetailViewController.xib in es.lproj from theDetailViewController.xib in en.lproj, and then replace all of the strings with the values fromDetailViewController.strings.”

Hit return. (You might see some sort of warning where ibtool complains about GSCapabilities; youcan ignore it.)

Open DetailViewController.xib (Spanish) in Xcode. This XIB file is now localized to Spanish. Tofinish things off, resize the label and text field for the serial number, as shown in Figure 17.4.

sloper
Highlight
sloper
Highlight
Page 22: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

UIPanGestureRecognizer and Simultaneous Recognizers

375

In TouchDrawView.m, return YES when the moveRecognizer sends the message to its delegate.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)other{ if (gestureRecognizer == moveRecognizer) return YES; return NO;}

Now when the user begins a long press, the UIPanGestureRecognizer will be allowed to keep track ofthis finger, too. When the finger begins to move, the pan recognizer will transition to the began state.If these two recognizers could not work simultaneously, the long press recognizer would start, and thepan recognizer would never transition to the began state or send its action message to its target.

In addition to the states we’ve seen previously, a pan gesture recognizer supports the changed state.When a finger starts to move, the pan recognizer enters the began state and sends a message to itstarget. While the finger moves around the screen, the recognizer transitions to the changed stateand sends its action message to its target repeatedly. Finally, when the finger leaves the screen, therecognizer’s state is set to ended, and the final message is delivered to the target.

Now we need to implement the moveLine: method that the pan recognizer sends its target. Inthis implementation, you will send the message translationInView: to the pan recognizer. ThisUIPanGestureRecognizer method returns how far the pan has moved as a CGPoint in the coordinatesystem of the view passed as the argument. Therefore, when the pan gesture begins, this property is setto the zero point (where both x and y equal zero). As the pan moves, this value is updated – if the pangoes very far to the right, it has a high x value; if the pan returns to where it began, its translation goesback to the zero point.

In TouchDrawView.m, implement moveLine:. Notice that because we will send the gesture recognizer amethod from the UIPanGestureRecognizer class, the parameter of this method must be a pointer to aninstance of UIPanGestureRecognizer rather than UIGestureRecognizer.

- (void)moveLine:(UIPanGestureRecognizer *)gr{ // If we haven't selected a line, we don't do anything here if (![self selectedLine]) return;

// When the pan recognizer changes its position... if ([gr state] == UIGestureRecognizerStateChanged) { // How far has the pan moved? CGPoint translation = [gr translationInView:self];

// Add the translation to the current begin and end points of the line CGPoint begin = [[self selectedLine] begin]; CGPoint end = [[self selectedLine] end]; begin.x += translation.x; begin.y += translation.y; end.x += translation.x; end.y += translation.y;

// Set the new beginning and end points of the line [[self selectedLine] setBegin:begin]; [[self selectedLine] setEnd:end];

sloper
Highlight
Page 23: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 23  Controlling Animation with CAAnimation

414

CAKeyframeAnimation *mover = [CAKeyframeAnimation animationWithKeyPath:@"position"];NSArray *vals = [NSMutableArray array];[vals addObject:[NSValue valueWithCGPoint:CGPointMake(0.0, 100.0)]];[vals addObject:[NSValue valueWithCGPoint:CGPointMake(100.0, 100.0)]];[mover setValues:vals];[mover setDuration:1.0];

Each value in the values property is called a keyframe. Keyframes are the values that the animationwill interpolate; the animation will take the property it is animating through each of these keyframesover its duration. A basic animation is really a keyframe animation that is limited to two keyframes. (Inaddition to allowing more than two keyframes, CAKeyframeAnimation adds the ability to change thetiming of each of the keyframes, but that’s more advanced than what we want to talk about right now.)

There are two more CAAnimation subclasses, but they are used less often. A CAAnimationGroupinstance holds an array of animation objects. When an animation group is added to a layer, theanimations run concurrently.

CABasicAnimation *mover = [CABasicAnimation animationWithKeyPath:@"position"];[mover setDuration:1.0];[mover setFromValue:[NSValue valueWithCGPoint:CGPointMake(0.0, 100.0)]];[mover setToValue:[NSValue valueWithCGPoint:CGPointMake(100.0, 100.0)]];

CABasicAnimation *fader = [CABasicAnimation animationWithKeyPath:@"opacity"];[fader setDuration:1.0];[fader setFromValue:[NSNumber numberWithFloat:1.0]];[fader setToValue:[NSNumber numberWithFloat:0.0]];

CAAnimationGroup *group = [CAAnimationGroup animation];[group setAnimations:[NSArray arrayWithObjects:fader, mover, nil]];

CATransition animates layers as they are transitioning on and off the screen. On Mac OS X,CATransition is made very powerful by Core Image Filters. In iOS, it can only do a couple of simpletransitions like fading and sliding. (CATransition is used by UINavigationController when pushinga view controller’s view onto the screen.)

Spinning with CABasicAnimationIn this section, you are going to use an animation object to spin the implicit layer of the time fieldin HypnoTime’s TimeViewController whenever it is updated (Figure 23.5). (Recall that an implicitlayer is a layer created by a view when the view is instantiated. The time field is a UILabel, which is asubclass of UIView, so it has an implicit layer that we can animate.)

sloper
Highlight
Page 24: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Constructing the tree of model objects

451

{ NSLog(@"%@ found a %@ element", self, elementName); if ([elementName isEqual:@"channel"]) {

// If the parser saw a channel, create new instance, store in our ivar channel = [[RSSChannel alloc] init];

// Give the channel object a pointer back to ourselves for later [channel setParentParserDelegate:self];

// Set the parser's delegate to the channel object // There will be a warning here, ignore it for now [parser setDelegate:channel]; }}

Build and run the application. You should see a log message that the channel was found. If you don’tsee this message, double-check that the URL you typed in fetchEntries is correct.

Now that the channel is sometimes the parser’s delegate, it needs to implement NSXMLParserDelegatemethods to handle the XML. The RSSChannel instance will catch the metadata it cares about alongwith any item elements.

The channel is interested in the title and description metadata elements, and you will store thosestrings that the parser finds in the appropriate instance variables. When the start of one of theseelements is found, an NSMutableString instance will be created. When the parser finds a string, thatstring will be concatenated to the mutable string.

In RSSChannel.h, declare that the class conforms to NSXMLParserDelegate and add an instancevariable for the mutable string.

@interface RSSChannel : NSObject <NSXMLParserDelegate>{ NSMutableString *currentString;}

In RSSChannel.m, implement one of the NSXMLParserDelegate methods to catch the metadata.

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict{ NSLog(@"\t%@ found a %@ element", self, elementName);

if ([elementName isEqual:@"title"]) { currentString = [[NSMutableString alloc] init]; [self setTitle:currentString]; } else if ([elementName isEqual:@"description"]) { currentString = [[NSMutableString alloc] init]; [self setInfoString:currentString]; }}

Note that currentString points at the same object as the appropriate instance variable – either titleor infoString (Figure 25.9).

sloper
Highlight
Page 25: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 25  Web Services and UIWebView

458

// Construct a URL with the link string of the item NSURL *url = [NSURL URLWithString:[entry link]];

// Construct a request object with that URL NSURLRequest *req = [NSURLRequest requestWithURL:url];

// Load the request into the web view [[webViewController webView] loadRequest:req];

// Set the title of the web view controller's navigation item [[webViewController navigationItem] setTitle:[entry title]];}

Build and run the application. You should be able to select one of the posts, and it should take you to anew view controller that displays the web page for that post.

For the More Curious: NSXMLParserNSXMLParser is the built-in XML parser in the iOS SDK. While there are plenty of parsers you canpick up on the Internet, adding a third party dependency is sometimes difficult. Many developers,seeing that NSXMLParser is not a tree-based parser (it doesn’t create an object graph out of the box), gosearching for an alternative parser. However, in this chapter, you’ve learned how to make NSXMLParserinto a tree-based parser.

To parse simple XML, all you need are the three delegate methods used in this chapter. Morecomplex XML has element attributes, namespaces, CDATA, and a slew of other items that needto be handled. NSXMLParser can handle these, too. The NSXMLParserDelegate protocol includesmany more methods that handle nearly anything XML can throw at you. There are also argumentsto the methods you have already used that can handle more complex XML. For example, inparser:didStartElement:namespaceURI:qualifiedName:attributes:, we only used the first twoarguments. For the other arguments, consider the following XML:

<?xml version="1.0" encoding="utf-8"?><container version="2.0" xmlns:foo="BNR"> <foo:item attribute1="one" attribute2="two"></item></container>

When the foo:item element is encountered by the parser, the values for the parameters to the delegatemethod are as follows:

• The element is “item.” The namespace is ignored, and the name of the element is kept.

• The namespaceURI is “BNR.” The element’s name is item, and it is in the foo namespace, which hasa value of “BNR.”

• The qualifiedName is “foo:item.”

• attributes is a dictionary that contains two keys, “attribute1” and “attribute2.” Their values are“one” and “two,” respectively.

One thing NSXMLParser can’t do is resolve XPaths. You have to use another library to handle this. (Formore information, check out the Tree-Based XML Programming Guide in the Apple documentation.)

sloper
Highlight
Page 26: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Chapter 28  Model-View-Controller-Store

524

- (void)readFromJSONDictionary:(NSDictionary *)d{ // The top-level object contains a "feed" object, which is the channel. NSDictionary *feed = [d objectForKey:@"feed"];

// The feed has a title property, make this the title of our channel. [self setTitle:[[feed objectForKey:@"title"] objectForKey:@"label"]];

// The feed also has an array of entries, for each one, make a new RSSItem. NSArray *entries = [feed objectForKey:@"entry"]; for (NSDictionary *entry in entries) { RSSItem *i = [[RSSItem alloc] init];

// Pass the entry dictionary to the item so it can grab its ivars [i readFromJSONDictionary:entry];

[items addObject:i]; }}

Do the same thing in RSSItem.h.

#import "JSONSerializable.h"

@interface RSSItem : NSObject <NSXMLParserDelegate, JSONSerializable>

And in RSSItem.m, grab the title and link from the entry dictionary.

- (void)readFromJSONDictionary:(NSDictionary *)d{ [self setTitle:[[d objectForKey:@"title"] objectForKey:@"label"]];

// Inside each entry is an array of links, each has an attribute object NSArray *links = [d objectForKey:@"link"]; if ([links count] > 1) { NSDictionary *sampleDict = [[links objectAtIndex:1] objectForKey:@"attributes"];

// The href of an attribute object is the URL for the sample audio file [self setLink:[sampleDict objectForKey:@"href"]]; }}

You can now build and run the application. Once it starts running, flip to Apple’s RSS feed. Voilà!Now that the RSSItem is grabbing the link, you can tap an entry in the table view and listen to a sampleclip. (The debugger will spit out a lot of angry nonsense about not finding functions or frameworks, butafter a moment, it will shut up, and the clip will play.)

Notice that, in MVCS (and in MVC, too), model objects are responsible for constructing themselvesgiven a stream of data. For example, RSSChannel conforms to both NSXMLParserDelegate andJSONSerializable – it knows how to pack and unpack itself from XML and JSON data. Also, thinkabout BNRItem instances in Homepwner: they implemented the methods from NSCoding, so that theycould be written to disk.

A model object, then, provides the instructions on how it is to be transferred into and out of differentformats. Model objects do this so that when a store object transfers them to or from an external source,the store doesn’t need to know the details of every kind of model object in an application. Instead, each

sloper
Highlight
Page 27: iOS Programming: The Big Nerd Ranch Guide  by Joe Conway and Aaron Hillegass

Advanced Caching

539

- (RSSChannel *)fetchRSSFeedWithCompletion: (void (^)(RSSChannel *obj, NSError *err))block;

In BNRFeedStore.m, update the completion block for fetchRSSFeedWithCompletion: to merge theincoming channel with the existing channel and cache it.

- (void)fetchRSSFeedWithCompletion:(void (^)(RSSChannel *obj, NSError *err))block- (RSSChannel *)fetchRSSFeedWithCompletion: (void (^)(RSSChannel *obj, NSError *err))block{ NSURL *url = [NSURL URLWithString:@"http://forums.bignerdranch.com/" @"smartfeed.php?limit=1_DAY&sort_by=standard" @"&feed_type=RSS2.0&feed_style=COMPACT"];

NSURLRequest *req = [NSURLRequest requestWithURL:url]; RSSChannel *channel = [[RSSChannel alloc] init]; BNRConnection *connection = [[BNRConnection alloc] initWithRequest:req]; [connection setCompletionBlock:block];

NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];

cachePath = [cachePath stringByAppendingPathComponent:@"nerd.archive"];

// Load the cached channel RSSChannel *cachedChannel = [NSKeyedUnarchiver unarchiveObjectWithFile:cachePath];

// If one hasn't already been cached, create a blank one to fill up if (!cachedChannel) cachedChannel = [[RSSChannel alloc] init];

[connection setCompletionBlock:^(RSSChannel *obj, NSError *err) { // This is the store's callback code if (!err) { [cachedChannel addItemsFromChannel:obj]; [NSKeyedArchiver archiveRootObject:cachedChannel toFile:cachePath]; }

// This is the controller's callback code block(cachedChannel, err); }];

[connection setXmlRootObject:channel]; [connection start];

return cachedChannel;}

Let’s follow the logic of this method. When the request is made, the cached RSSChannel is loaded fromthe filesystem. A second, empty instance of RSSChannel is set as the xmlRootObject of the connection.When the service completes, the xmlRootObject will contain the new items from the server. Thischannel is then merged into the existing cachedChannel (Figure 29.3). The merged channel is cachedand passed to the controller via its completion block. At the end of this method, the cached channel(before being merged) is returned to the controller.

sloper
Highlight