Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 58 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
58
Dung lượng
2,19 MB
Nội dung
CHAPTER 9: Navigation Controllers and Table Views264 } - (void)viewDidUnload { self.list = nil; [childController release]; childController = nil; } - (void)dealloc { [list release]; [childController release]; [super dealloc]; } #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] autorelease]; } NSUInteger row = [indexPath row]; NSString *rowString = [list objectAtIndex:row]; cell.textLabel.text = rowString; cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; [rowString release]; 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" 24594ch09.indd 264 6/23/09 11:45:28 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views 265 otherButtonTitles:nil]; [alert show]; [alert release]; } - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath { if (childController == nil) childController = [[DisclosureDetailController alloc] initWithNibName:@"DisclosureDetail" 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; [detailMessage release]; [self.navigationController pushViewController:childController animated:YES]; } @end By now, you should be fairly comfortable with everything up to and including the three datasource methods we just added. Let’s look at our two new delegate methods. The first method, tableView:didSelectRowAtIndexPath:, which gets called when the row is selected, 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 last of our new delegate methods, tableView:accessoryButtonTappedForRowWithIndexPath:, is called. Let’s look at this one a little more closely. The first thing we do here is check the childController instance variable to see if it’s nil. If it is, we have not yet allocated and initialized a new instance of DetailDisclosure Controller , so we do that next. if (childController == nil) childController = [[DisclosureDetailController alloc] initWithNibName:@"DisclosureDetail" bundle:nil]; This gives us a new controller that we can push onto the navigation stack, just as we did earlier in FirstLevelViewController. Before we push it onto the stack, though, we need to give it some text to display. childController.title = @"Disclosure Button Pressed"; 24594ch09.indd 265 6/23/09 11:45:28 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views266 In this case, we set message to reflect the row whose disclosure button was tapped. We also set the new view’s title based on the selected row. 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; [detailMessage release]; Then, finally, we push the detail view controller onto the navigation stack. [self.navigationController pushViewController:childController animated:YES]; And, with that, our first second-level controller is done, as is our detail controller. The only remaining task is to create an instance of our second level controller and add it to FirstLevelViewController’s controllers. Single-click FirstLevelViewController.m, and insert the following code into the viewDidLoad method: - (void)viewDidLoad { self.title = @"First Level"; NSMutableArray *array = [[NSMutableArray alloc] init]; // Disclosure Button DisclosureButtonController *disclosureButtonController = [[DisclosureButtonController alloc] initWithStyle:UITableViewStylePlain]; disclosureButtonController.title = @"Disclosure Buttons"; disclosureButtonController.rowImage = [UIImage imageNamed:@"disclosureButtonControllerIcon.png"]; [array addObject:disclosureButtonController]; [disclosureButtonController release]; self.controllers = array; [array release]; [super viewDidLoad]; } All that we’re doing is creating a new instance of DisclosureButtonController. We specify UITableViewStylePlain to indicate that we want an indexed table, not a grouped table. Next, we set the title and the image to one of the .png files we had you add to your project, 24594ch09.indd 266 6/23/09 11:45:28 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views 267 add the controller to the array, and release the controller. Up at the top of the file, you’ll need to add one line of code to import the header class for our new file. Insert this line right above the @implementation declaration: #import "DisclosureButtonController.h" Save everything, and try building. If everything went as planned, your project should com- pile and then launch in the simulator. When it comes up, there should be just a single row (see Figure 9-12). If you touch the one row, it will take you down to the table view we just implemented (see Figure 9-13). Notice that the title that we set for our controller is now displayed in the navigation bar, and the title of the view controller we were previously at (First Level) is contained in a navigation button. Tapping that button will take the user back up to the first level. Select any row in this table, and you will get a gentle reminder that the detail disclosure button is there for drilling down (see Figure 9-14). Figure 9-12. Our application after adding the first of six second-level controllers Figure 9-13. The Disclosure Buttons view Figure 9-14. Selecting the row does not drill down when there is a detail disclosure button visible. 24594ch09.indd 267 6/23/09 11:45:28 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views268 If you touch the detail disclosure button itself, you drill down into another view. The new view (see Figure 9-15) shows information that we passed into it. Even though this is a simple example, the same basic technique is used any- time you show a detail view. Notice that when we drill down to the detail view, the title again changes, as does the back button, which now takes us to the previous view instead of the root view. That finishes up the first view controller. Do you see now how the design Apple used here with the navigation controller makes it possible to build your application in small chunks? That’s pretty cool, isn’t it? Our Second Subcontroller: The Checklist The next second-level view we’re going to implement is another table view, but this time, we’re going to use the accessory icon to let the user select one and only one item from the list. We’ll use the acces- sory icon to place a checkmark next to the currently selected row, and we’ll change the selection when the user touches another row. Since this view is a table view and it has no detail view, we don’t need a new nib, but we do need to create another subclass of SecondLevelViewController. Select the Classes folder in the Groups & Files pane in Xcode, and then press ⌘N or select New File. . . from the File menu. Select Cocoa Touch Class, and then select Objective-C class and NSObject for Subclass of. Click the Next button, and when prompted for a name, type CheckListController.m, and make sure that the header file is created as well. To present a checklist, we’re going to need a way to keep track of which row is currently selected. We’ll declare an NSIndexPath property to track the last row selected. Single-click CheckListController.h, and add the following code: #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #import "SecondLevelViewController.h" @interface CheckListController : NSObject { @interface CheckListController : SecondLevelViewController { NSArray *list; NSIndexPath *lastIndexPath; } Figure 9-15. The detail view 24594ch09.indd 268 6/23/09 11:45:28 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views 269 @property (nonatomic, retain) NSArray *list; @property (nonatomic, retain) NSIndexPath * lastIndexPath; @end Then switch over CheckListController.m and add the following code: #import "CheckListController.h" @implementation CheckListController @synthesize list; @synthesize lastIndexPath; - (void)viewDidLoad { NSArray *array = [[NSArray alloc] initWithObjects:@"Who Hash", @"Bubba Gump Shrimp Étouffée", @"Who Pudding", @"Scooby Snacks", @"Everlasting Gobstopper", @"Green Eggs and Ham", @"Soylent Green", @"Hard Tack", @"Lembas Bread", @"Roast Beast", @"Blancmange", nil]; self.list = array; [array release]; [super viewDidLoad]; } - (void)viewDidUnload { self.list = nil; self.lastIndexPath = nil; [super viewDidUnload]; } - (void)dealloc { [list release]; [lastIndexPath release]; [super dealloc]; } #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 *CheckMarkCellIdentifier = @"CheckMarkCellIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CheckMarkCellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 24594ch09.indd 269 6/23/09 11:45:28 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views270 reuseIdentifier:CheckMarkCellIdentifier] autorelease]; } NSUInteger row = [indexPath row]; NSUInteger oldRow = [lastIndexPath row]; cell.textLabel.text = [list objectAtIndex:row]; cell.accessoryType = (row == oldRow && lastIndexPath != nil) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; return cell; } #pragma mark - #pragma mark Table Delegate Methods - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { int newRow = [indexPath row]; int oldRow = (lastIndexPath != nil) ? [lastIndexPath row] : -1; if (newRow != oldRow) { UITableViewCell *newCell = [tableView cellForRowAtIndexPath: indexPath]; newCell.accessoryType = UITableViewCellAccessoryCheckmark; UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: lastIndexPath]; oldCell.accessoryType = UITableViewCellAccessoryNone; lastIndexPath = indexPath; } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } @end Look first at the tableView:cellForRowAtIndexPath: method, because there are a few new things in there worth noticing. The first several lines should be familiar to you: static NSString *CheckMarkCellIdentifier = @"CheckMarkCellIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CheckMarkCellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CheckMarkCellIdentifier] autorelease]; } 24594ch09.indd 270 6/23/09 11:45:28 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views 271 Here’s where things get interesting, though. First, we extract the row from this cell and from the current selection: NSUInteger row = [indexPath row]; NSUInteger oldRow = [lastIndexPath row]; We grab the value for this row from our array and assign it to the cell’s title: cell.textLabel.text = [list objectAtIndex:row]; Then, we set the accessory to show either a checkmark or nothing, depending on whether the two rows are the same. In other words, if the row the table is requesting a cell for is the currently selected row, we set the accessory icon to be a checkmark; otherwise, we set it to be nothing. Notice that we also check lastIndexPath to make sure it’s not nil. We do this because a nil lastIndexPath indicates no selection. However, calling the row method on a nil object will return a 0, which is a valid row, but we don’t want to put a checkmark on row 0 when, in reality, there is no selection. cell.accessoryType = (row == oldRow && lastIndexPath != nil) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone; After that, we just release the string we declared and return the cell. [rowTitle release]; return cell; Now skip down to the last method. You’ve seen the tableView:didSelectRowAtIndexPath: method before, but we’re doing something new here. We grab not only the row that was just selected but also the row that was previously selected. int newRow = [indexPath row]; int oldRow = [lastIndexPath row]; We do this so if the new row and the old row are the same, we don’t bother making any changes: if (newRow != oldRow) { Next, we grab the cell that was just selected and assign a checkmark as its accessory icon: UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath]; newCell.accessoryType = UITableViewCellAccessoryCheckmark; 24594ch09.indd 271 6/23/09 11:45:29 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views272 We then grab the previously selected cell, and we set its accessory icon to none: UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: lastIndexPath]; oldCell.accessoryType = UITableViewCellAccessoryNone; After that, we store the index path that was just selected in lastIndexPath, so we’ll have it next time a row is selected: lastIndexPath = indexPath; } When we’re all done, we tell the table view to deselect the row that was just selected, because we don’t want the row to stay highlighted. We’ve already marked the row with a checkmark; leaving it blue would just be a distraction. [tableView deselectRowAtIndexPath:indexPath animated:YES]; } Next, we just need to add an instance of this controller to FirstLevelViewController’s controllers array. We do that by adding the following code to the viewDidLoad method in FirstLevelViewController.m: - (void)viewDidLoad { self.title = @"First Level"; NSMutableArray *array = [[NSMutableArray alloc] init]; // Disclosure Button DisclosureButtonController *disclosureButtonController = [[DisclosureButtonController alloc] initWithStyle:UITableViewStylePlain]; disclosureButtonController.title = @"Disclosure Buttons"; disclosureButtonController.rowImage = [UIImage imageNamed: @"disclosureButtonControllerIcon.png"]; [array addObject:disclosureButtonController]; [disclosureButtonController release]; // Check List CheckListController *checkListController = [[CheckListController alloc] initWithStyle:UITableViewStylePlain]; checkListController.title = @"Check One"; checkListController.rowImage = [UIImage imageNamed: @"checkmarkControllerIcon.png"]; [array addObject:checkListController]; [checkListController release]; 24594ch09.indd 272 6/23/09 11:45:29 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views 273 self.controllers = array; [array release]; [super viewDidLoad]; } Finally, you’ll need to import the new header file, so add this line just after all the other #import statements, toward the top of the file: #import "CheckListController.h" Well, what are you waiting for? Save everything, compile, and run. If everything went smoothly, the application launched again in the simulator, and there was much rejoicing. This time there will be two rows (see Figure 9-16). If you touch the Check One row, it will take you down to the view controller we just implemented (see Figure 9-17). When it first comes up, no rows will be selected and no checkmarks will be visible. If you tap a row, a checkmark will appear. If you then tap a different row, the checkmark will switch to the new row. Huzzah! Figure 9-16. Two second-level controllers, two rows. What a coincidence! Figure 9-17. The checklist view. Note that only a single item can be checked at a time. Soylent Green, anyone? 24594ch09.indd 273 6/23/09 11:45:29 AM Download at Boykma.Com [...]... accessory pane this time, which means that when tapping the accessory pane, the user will tap the button, similar to the way they would tap a disclosure button To add another row to our root view’s table, we need another controller You know the drill: select the Classes folder in the Groups & Files pane in Xcode, and then press ⌘N or select New File from the File menu Select Cocoa Touch Class, select Objective-C... Download at Boykma.Com 24594ch09.indd 277 6/ 23/ 09 11:45:29 AM 278 CHAPTER 9: Navigation Controllers and Table Views To create the button, we’re going to load in two of the images that were in the images folder you imported earlier One will represent the button in the normal state, the other will represent the button in its highlighted state or, in other words, when the button is being tapped: UIImage *buttonUpImage... launch in the simulator with (count ’em) four rows in the root-level table If you click the new one, called Move Me, it’ll take you down to a list of rows If you want to try moving the rows, click the Move button, and the reorder controls should appear (see Figure 9-20) If you tap in the reorder control and then drag, the row should move as you drag, as in Figure 9 -6 Move the row as you like The row... to follow, but the delete and reorder operations do play nicely together A row that can be reordered will display the reorder icon anytime that the table is in edit mode When you tap the red circular icon on the left side of the row (see Figure 9-7), the Delete button will pop up, obscuring the reorder icon but only temporarily Download at Boykma.Com 24594ch09.indd 288 6/ 23/ 09 11:45 :30 AM CHAPTER 9: ... takes two Booleans The first indicates whether you are turning on or off editing mode, and the second indicates whether the table should animate the transition If you set editing to the mode it’s already in (in other words, turning it on when it’s already on or off when it’s already off ), the transition will not be animated regardless of what you specify in the second parameter In the follow-on controller,... tableView:moveRowAtIndexPath:fromIndexPath:, is the method that will actually get called when the user moves a row The two parameters besides tableView are both NSIndexPath instances that identify the row that was moved and the row’s new position The table view has already moved the rows in the table so the user is seeing the right thing, but we need to update our data model to keep the two in sync and avoid causing... toggleMove method whenever the button is tapped As a result, anytime the user taps this button, editing mode will be toggled After we create the button, we add it to the right side of the navigation bar, and then release it Now, skip down to the tableView:cellForRowAtIndexPath: method we just added Did you notice this new line of code? Download at Boykma.Com 24594ch09.indd 284 6/ 23/ 09 11:45 :30 AM CHAPTER 9: ... specifying the new object, the system will place in the appropriate row We won’t be covering the use of inserts, but the insert functionality works in fundamentally the same way as the delete we are about to implement The only difference is that, instead of deleting the specified row from your data model, you have to create a new object and insert it at the specified spot The last parameter, indexPath,... path represents the row to be deleted For an insert, it represents the index where the new row should be inserted In our method, we first retrieve the row that is being edited from indexPath: NSUInteger row = [indexPath row]; Then, we remove the object from the mutable array we created earlier: [self.list removeObjectAtIndex:row]; Finally, we tell the table to delete the row, specifying the constant UITableViewRow... hold the individual instances that will be displayed in the list Figure 9- 23 An example of a grouped table view being used to present an editable table view Creating the Data Model Object The property list we’ll be using in this section of the application contains data about the US presidents: each president’s name, his party, the year he took office, and the year he left office Let’s create the class . objectAtIndex:row]; Then, we set the accessory to show either a checkmark or nothing, depending on whether the two rows are the same. In other words, if the row the table is requesting a cell for is the currently. Controllers and Table Views 267 add the controller to the array, and release the controller. Up at the top of the file, you’ll need to add one line of code to import the header class for our new. visible. 24594ch09.indd 267 6/ 23/ 09 11:45:28 AM Download at Boykma.Com CHAPTER 9: Navigation Controllers and Table Views 268 If you touch the detail disclosure button itself, you drill down into another view. The