Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Beginning iOS5 Development.pdf
Скачиваний:
7
Добавлен:
09.05.2015
Размер:
15.6 Mб
Скачать

CHAPTER 9: Navigation Controllers and Table Views

295

First Subcontroller: The Disclosure Button View

Let’s implement the first of our second-level view controllers. To do that, we’ll need to create a subclass of BIDSecondLevelViewController.

In the project navigator, select the Nav folder and press N to bring up the new file assistant. Select Cocoa Touch in the left pane, and then select Objective-C class and click Next. On the following screen, name the class BIDDisclosureButtonController and enter BIDSecondLevelViewController for Subclass of. Remember to check your spelling! This class will manage the table of movie names that will be displayed when the user clicks the Disclosure Buttons item from the top-level view (see Figure 9–3).

Creating the Detail View

When the user clicks any movie title, the application will drill down into another view that will report which row was selected. So, we also need to create a detail view for the user to drill down to. Repeat the steps we just took to create another Objective-C class called

BIDDisclosureDetailController, this time using UIViewController as the superclass. Again, be sure to check your spelling.

NOTE: Just a reminder: BIDDisclosureButtonController keeps track of the table of movie names, while BIDDisclosureDetailController manages the next level down, which is the detail view that is pushed on the navigation stack when a specific movie is selected.

The detail view will hold just a single label that we can set. It won’t be editable; we’ll just use it to show how to pass values into a child controller. Because this controller will not be responsible for a table view, we also need a nib file to go along with the controller class. Before we create the nib, let’s quickly add the outlet for the label. Make the following changes to BIDDisclosureDetailController.h:

#import <UIKit/UIKit.h>

@interface BIDDisclosureDetailController : UIViewController

@property (strong, nonatomic) IBOutlet UILabel *label; @property (copy, nonatomic) NSString *message;

@end

Why, pray tell, are we adding both a label and a string? Remember the concept of lazy loading? Well, view controllers use lazy loading behind the scenes as well. When we create our controller, it won’t load its nib file until it is actually displayed. When the controller is pushed onto the navigation controller’s stack, we can’t count on there being a label to set. If the nib file has not been loaded, label will just be a pointer set to nil. But it’s OK. Instead, we’ll set message to the value we want, and in the viewWillAppear: method, we’ll set label based on the value in message.

www.it-ebooks.info

296

CHAPTER 9: Navigation Controllers and Table Views

Why are we using viewWillAppear: to do our updating instead of using viewDidLoad, as we’ve done in the past? The problem is that viewDidLoad is called only the first time the controller’s view is loaded. But in our case, we are reusing the BIDDisclosureDetailController’s view. No matter which fine Pixar flick you pick, when you tap the disclosure button, the detail message appears in the same

BIDDisclosureDetailController view. If we used viewDidLoad to manage our updates, that view would be updated only the first time the BIDDisclosureDetailController view appeared. When we picked our second fine Pixar flick, we would still see the detail message from the first fine Pixar flick (try saying that ten times fast)—not good. Since viewWillAppear: is called every time a view is about to be drawn, we’ll be fine using it for our updating.

Going back to the property declarations, you may notice that the message property is declared using the copy keyword instead of strong. What’s up with that? Why should we be copying strings willy-nilly? The reason is the potential existence of mutable strings.

Imagine we had declared the property using strong, and an outside piece of code passed in an instance of NSMutableString to set the value of the message property. This is something that often happens when you’re dealing with strings entered by the user in a user interface object. If that original caller later decides to change the content of that string, the BIDDisclosureDetailController instance will end up in an inconsistent state, where the value of message and the value displayed in the text field aren’t the same! Using copy eliminates that risk, since calling copy on any NSString (including subclasses that are mutable) always gives us an immutable copy. Also, we don’t need to worry about the performance impact too much. As it turns out, sending copy to any immutable string instance doesn’t actually copy the string. Instead, it returns the same string object, after increasing its reference count. In effect, calling copy on an immutable string is the same as calling retain, which is what ARC might do behind the scenes anytime you set a strong property. So, it works out just fine for everyone, since the object can never change.

Add the following code to BIDDisclosureDetailController.m:

#import "BIDDisclosureDetailController.h"

@implementation BIDDisclosureDetailController

@synthesize label; @synthesize message;

- (void)viewWillAppear:(BOOL)animated { label.text = message;

[super viewWillAppear:animated];

}

- (void)viewDidUnload { self.label = nil; self.message = nil; [super viewDidUnload];

}

@end

www.it-ebooks.info

CHAPTER 9: Navigation Controllers and Table Views

297

That’s all pretty straightforward, right? Now, let’s create the nib to go along with this source code. Be sure you’ve saved your source changes.

Select the Nav folder in the project navigator, and press N to create another new file. This time, select User Interface from the iOS section on the left pane and View from the upper right. Then click Next. On the next screen, set Device Family to iPhone. Move to the next screen, and name this file BIDDisclosureDetail.xib. This file will implement the view seen when the user taps one of the movie buttons.

Select BIDDisclosureDetail.xib in the project navigator to open the file for editing. Once it’s open, single-click File’s Owner, and press 3 to bring up the identity inspector. Change the underlying class to BIDDisclosureDetailController. Now control-drag from the File’s Owner icon to the View icon, and select the view outlet to establish a link from the controller to its view.

Drag a Label from the library, and place it on the View window, centering the label both vertically and horizontally. It doesn’t need to be perfectly centered. Resize the label so it stretches from the left blue guideline to the right blue guideline, and then use the attributes inspector ( 4) to change the text alignment to centered. Control-drag from File’s Owner to the label, and select the label outlet. Save your changes.

Modifying the Disclosure Button Controller

For this example, our table of movies will base its data on rows from an array, so we will declare an NSArray named list to serve that purpose. We also need to declare a property to hold one instance of our child controller, which will point to an instance of the BIDDisclosureDetailController class we just built. We could allocate a new instance of that controller class every time the user taps a detail disclosure button, but it’s more efficient to create one and then keep reusing it. Make the following changes to

BIDDisclosureButtonController.h:

#import "BIDSecondLevelViewController.h"

@interface BIDDisclosureButtonController : BIDSecondLevelViewController

@property (strong, nonatomic) NSArray *list;

@end

Now we get to the juicy part. Add the following code to BIDDisclosureButtonController.m. We’ll talk about what’s going on afterward.

#import "BIDDisclosureButtonController.h"

#import "BIDAppDelegate.h"

#import "BIDDisclosureDetailController.h"

@interface BIDDisclosureButtonController ()

@property (strong, nonatomic) BIDDisclosureDetailController *childController; @end

@implementation BIDDisclosureButtonController

@synthesize list; @synthesize childController;

www.it-ebooks.info

298

CHAPTER 9: Navigation Controllers and Table Views

- (void)viewDidLoad { [super viewDidLoad];

NSArray *array = [[NSArray alloc] initWithObjects:@"Toy Story",

@"A Bug's Life", @"Toy Story 2", @"Monsters, Inc.", @"Finding Nemo", @"The Incredibles", @"Cars", @"Ratatouille", @"WALL-E", @"Up", @"Toy Story 3", @"Cars 2", @"Brave", nil];

self.list = array;

}

-(void)viewDidUnload { [super viewDidUnload]; self.list = nil;

self.childController = nil;

}

#pragma mark -

#pragma mark Table Data Source Methods

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

return [list count];

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString * DisclosureButtonCellIdentifier = @"DisclosureButtonCellIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: DisclosureButtonCellIdentifier];

if (cell == nil) {

cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: DisclosureButtonCellIdentifier];

}

NSUInteger row = [indexPath row];

NSString *rowString = [list objectAtIndex:row]; cell.textLabel.text = rowString;

cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; return cell;

}

#pragma mark -

#pragma mark Table Delegate Methods

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"Hey, do you see the disclosure button?"

message:@"If you're trying to drill down, touch that instead" delegate:nil

cancelButtonTitle:@"Won't happen again" otherButtonTitles:nil];

www.it-ebooks.info

CHAPTER 9: Navigation Controllers and Table Views

299

[alert show];

}

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {

if (childController == nil) {

childController = [[BIDDisclosureDetailController alloc] initWithNibName:@"BIDDisclosureDetail" bundle:nil];

}

childController.title = @"Disclosure Button Pressed"; NSUInteger row = [indexPath row];

NSString *selectedMovie = [list objectAtIndex:row]; NSString *detailMessage = [[NSString alloc]

initWithFormat:@"You pressed the disclosure button for %@.", selectedMovie];

childController.message = detailMessage; childController.title = selectedMovie; [self.navigationController pushViewController:childController

animated:YES];

}

@end

Right near the top of that big chunk, you may have noticed the following @interface declaration, just where you may have expected an @implementation section to start instead:

@interface BIDDisclosureButtonController ()

@property (strong, nonatomic) BIDDisclosureDetailController *childController; @end

This kind of category declaration, where the parentheses are empty rather than containing the name of the category you’re declaring, is called a class extension. This is a handy place to declare properties and methods that will be in the main @implementation section containing your class, but that you don’t want to show up in the public header file.

A class extension is a good place to put a property for the childController. We are using this property internally in our class and don’t want to expose it to others, so we don’t advertise its existence by declaring it in the header.

By now, you should be fairly comfortable with pretty much everything up to and including the three data source methods we just wrote. Let’s look at the two delegate methods we added, which you haven’t seen before.

The first method, tableView:didSelectRowAtIndexPath:, is called when the row is selected. It puts up a polite little alert telling the user to tap the disclosure button instead of selecting the row. If the user actually taps the detail disclosure button, the other one of our new delegate methods, tableView:accessoryButtonTappedForRowWithIndexPath:, is called.

The first thing we do in tableView:accessoryButtonTappedForRowWithIndexPath: is check the childController instance variable to see if it’s nil. If it is, we have not yet

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]