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

286

CHAPTER 9: Navigation Controllers and Table Views

The Editable Detail View

The sixth and last of our application’s subcontrollers is shown in Figure 9–8. It shows an editable detail view using a grouped table. This technique for detail views is used widely by the applications that ship on the iPhone.

Figure 9–8. The sixth and last of the Nav application’s subcontrollers implements an editable detail view using a grouped table.

We have so very much to do. Let’s get started!

The Nav Application’s Skeleton

Xcode offers a perfectly good template for creating navigation-based applications, and you will likely use it much of the time when you need to create hierarchical applications. However, we’re not going to use that template today. Instead, we’ll construct our navigation-based application from the ground up so you get a feel for how everything fits together. It’s not really much different from the way we built the tab bar controller in Chapter 7, so you shouldn’t have any problems keeping up.

In Xcode, press N to create a new project, select Empty Application from the iOS Application template list, and then click Next to continue. Set Nav as the Product Name, com.apress as the Company Identifier, and BID as the Class Prefix. Make sure that Use

www.it-ebooks.info

CHAPTER 9: Navigation Controllers and Table Views

287

Core Data and Include Unit Tests are not checked, that Use Automatic Reference Counting is checked, and that Device Family is set to iPhone.

As you’ll see if you select the project navigator and open the Nav folder, this template gives you an application delegate and not much else. At this point, there are no view controllers or navigation controllers.

To make this app run, we’ll need to add a navigation controller, which includes a navigation bar. We’ll also need to add a series of views and view controllers for the navigation bar to show. The first of these views is the top-level view shown in Figure 9–2.

Each row in that top-level view is tied to a child view controller, as shown in Figures 9–3 through 9–8. Don’t worry about the specifics. You’ll see how those connections work as you make your way through the chapter.

Creating the Top-Level View Controller

In this chapter, we’re going to subclass UITableViewController instead of

UIViewController for our table views. When we subclass UITableViewController, we inherit some nice functionality from that class that will create a table view with no need for a nib file. We can provide a table view in a nib, as we did in the previous chapter, but if we don’t, UITableViewController will create a table view automatically. This table view will take up the entire space available and will connect the appropriate outlets in our controller class, making our controller class the delegate and data source for that table. When all you need for a specific controller is a table, subclassing

UITableViewController is the way to go.

We’ll create one class called BIDFirstLevelController that represents the first level in the navigation hierarchy. That’s the table that contains one row for each of the secondlevel table views. Those second-level table views will each be represented by the BIDSecondLevelViewController class. You’ll see how all this works as you make your way through the chapter.

In your project window, select the Nav folder in the project navigator, and then press N or select File New New File…. When the new file assistant comes up, select Cocoa Touch, select Objective-C class, and then click Next. On the next screen, enter

BIDFirstLevelController in the Class field, and type UITableViewController in the

Subclass of field. As always, be sure to check your spelling carefully before you click Next. Then make sure the Nav folder or group is selected in the file browser, Group, and Target controls before clicking Create.

You may have noticed an entry named UIViewController in the file template selector. That option provides you with a number of empty “stub” methods as a starting point to build a view controller, and even lets you pick a subclass of UIViewController, such as UITableViewController, with even more empty methods just waiting for you to plug in additional functionality. When creating your own applications, feel free to use those templates. We didn’t use any of the view controller templates here, so we wouldn’t need to spend time sorting through all the unneeded template methods, working out where to

www.it-ebooks.info

288

CHAPTER 9: Navigation Controllers and Table Views

insert or delete code. By creating a plain Objective-C object, and simply setting its superclass to UITableViewController, we get a smaller, more manageable file.

Once the files have been created, single-click BIDFirstLevelController.h, and take a look at it.

#import <UIKit/UIKit.h>

@interface BIDFirstLevelController : UITableViewController

@end

Since the class we chose to subclass from is a UIKit class, Xcode handily imported UIKit instead of just Foundation. The two files we just created contain the controller class for the top-level view, as shown in Figure 9–2. Our next step is to set up our navigation controller.

Setting Up the Navigation Controller

Our goal here is to edit the application delegate to add our navigation controller’s view to the application window.

Let’s start by editing BIDAppDelegate.h to add a property, navController, to point to our navigation controller:

#import <UIKit/UIKit.h>

@interface BIDAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) UINavigationController *navController;

@end

Next, we need to hop over to the implementation file, where we’ll import a header for the view controller class we just created and add the @synthesize statement for navController. In the application:didFinishLaunchingWithOptions: method, we’ll create navController, set it up with the initial view controller that it’s going to display, and add its view as a subview of our application’s window so that it is shown to the user. We’ll explain each of those steps in a moment. For now, select BIDAppDelegate.m, and make the following changes:

#import "BIDAppDelegate.h"

#import "BIDFirstLevelController.h"

@implementation BIDAppDelegate

@synthesize window = _window;

@synthesize navController;

#pragma mark -

#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

www.it-ebooks.info

CHAPTER 9: Navigation Controllers and Table Views

289

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch

BIDFirstLevelController *first = [[BIDFirstLevelController alloc] initWithStyle:UITableViewStylePlain];

self.navController = [[UINavigationController alloc] initWithRootViewController:first];

[self.window addSubview:navController.view];

self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];

return YES;

}

.

.

.

@end

The lines we added to the application:didFinishLaunchingWithOptions: method deserve some attention. The first thing we did was to create an instance of

BIDFirstLevelController.

BIDFirstLevelController *first = [[BIDFirstLevelController alloc] initWithStyle:UITableViewStylePlain];

Since BIDFirstLevelController is a subclass of UITableViewController, it can use the methods defined there, including the handy initWithStyle: method, which lets you create a controller whose table view will be either plain or grouped (whichever you choose), without the need for a nib file. Many iOS applications are built around table views whose appearance is dictated entirely by the cells they contain, and don’t need any nib customization for the table views themselves. Therefore, using the initWithStyle: method is a common shortcut for instantiating table view controllers without much ado.

Next, we created an instance of the navigation controller.

self.navController = [[UINavigationController alloc] initWithRootViewController:first];

Here, we see that UINavigationController, like UITableViewController, has its own special initializer. Here, the initWithRootViewController: method lets us pass in the top-level controller that the navigation control should use to display its initial content—in this case, the BIDFirstLevelController referenced by the first variable.

Finally, we added navController’s view to our window in order to display it.

[self.window addSubview:navController.view];

It’s worth taking a moment to think about this. What exactly is the view we’re passing with the addSubview: method? It’s a composite view provided by the navigation controller, which contains a combination of two things: the navigation bar at the top of the screen (which usually contains some sort of title and often a back button of some kind on the left),

www.it-ebooks.info

290

CHAPTER 9: Navigation Controllers and Table Views

and the content of whatever the navigation controller’s current view controller wants to display. In our case, the lower part of the display will be filled with the table view associated with the BIDFirstLevelController instance we created a few lines ago.

You’ll learn more about how to control what the navigation controller shows in the navigation bar as we go forward. You’ll also gain an understanding of how the navigation controller shifts focus from one subordinate view controller to another. For now, we’ve laid enough groundwork here that we can start defining what our own custom view controllers are going to do.

Now, we need a list of rows for our BIDFirstLevelController to display. In the previous chapter, we used simple arrays of strings to populate our table rows. In this application, the first-level view controller will manage a list of its subcontrollers, which we will be building throughout the chapter.

When we were designing this application, we decided that we wanted our first-level view controller to display an icon to the left of each of its subcontroller names. Instead of adding a UIImage property to every subcontroller, we’ll create a subclass of UITableViewController that has a UIImage property to hold the row icon. We will then subclass this new class instead of subclassing UITableViewController directly. As a result, all of our subclasses will get that UIImage property for free, which will make our code much cleaner.

NOTE: We will never actually create an instance of our new UITableViewController subclass. It exists solely to let us add a common item to the rest of the controllers we’re going to write. In many languages, we would declare this as an abstract class, but Objective-C doesn’t include any syntax to support abstract classes. We can make classes that aren't intended to be

instantiated, but the Objective-C compiler won't actually prevent us from writing code that creates instances of such a class, the way that the compilers for many other languages might. Objective-C is much more permissive than most other popular languages, and this can be a little

hard to get used to.

Single-click the Nav folder in Xcode, and then press N to bring up the new file assistant. Select Cocoa Touch from the left pane, select Objective-C class, and click Next. On the next screen, name the new class BIDSecondLevelViewController, and enter UITableViewController for Subclass of. Then click Next again, and go on and save the class files as usual. Once the new files are created, select

BIDSecondLevelViewController.h, and make the following changes:

#import <UIKit/UIKit.h>

@interface BIDSecondLevelViewController : UITableViewController

@property (strong, nonatomic) UIImage *rowImage;

@end

Over in BIDSecondLevelViewController.m, add the following line of code:

www.it-ebooks.info

CHAPTER 9: Navigation Controllers and Table Views

291

#import "BIDSecondLevelViewController.h"

@implementation BIDSecondLevelViewController

@synthesize rowImage;

@end

Any controller class that we want to implement as a second-level controller—in other words, any controller that the user can navigate to directly from the first table shown in our application—should subclass BIDSecondLevelViewController instead of

UITableViewController. Because we’re subclassing BIDSecondLevelViewController, all of those classes will have a property they can use to store a row image, and we can write our code in BIDFirstLevelController before we’ve actually written any concrete second-level controller classes by using BIDSecondLevelViewController as a placeholder.

Let’s implement our BIDFirstLevelController class now. Be sure to save the changes you made to BIDSecondLevelViewController. Then make these changes to

BIDFirstLevelController.h:

#import <UIKit/UIKit.h>

@interface BIDFirstLevelController : UITableViewController

@property (strong, nonatomic) NSArray *controllers;

@end

The array we just added will hold the instances of the second-level view controllers. We’ll use it to feed data to our table.

Add the following code to BIDFirstLevelController.m, and then come on back and gossip with us, ’K?

#import "BIDFirstLevelController.h"

#import "BIDSecondLevelViewController.h"

@implementation BIDFirstLevelController

@synthesize controllers;

-(void)viewDidLoad { [super viewDidLoad];

self.title = @"First Level";

NSMutableArray *array = [[NSMutableArray alloc] init];

self.controllers = array;

}

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

}

#pragma mark -

#pragma mark Table Data Source Methods

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

return [self.controllers count];

}

www.it-ebooks.info

292

CHAPTER 9: Navigation Controllers and Table Views

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

static NSString *FirstLevelCell = @"FirstLevelCell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: FirstLevelCell];

if (cell == nil) {

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

}

// Configure the cell

NSUInteger row = [indexPath row]; BIDSecondLevelViewController *controller =

[controllers objectAtIndex:row]; cell.textLabel.text = controller.title; cell.imageView.image = controller.rowImage;

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell;

}

#pragma mark -

#pragma mark Table View Delegate Methods

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

NSUInteger row = [indexPath row];

BIDSecondLevelViewController *nextController = [self.controllers objectAtIndex:row];

[self.navigationController pushViewController:nextController animated:YES];

}

@end

First, notice that we’ve imported that new BIDSecondLevelViewController.h header file. Doing that lets us use the BIDSecondLevelViewController class in our code so that the compiler will know about the rowImage property.

Next comes the viewDidLoad method. The first thing we do is set self.title. A navigation controller knows what to display in the title of its navigation bar by asking the currently active controller for its title. Therefore, it’s important to set the title for all controller instances in a navigation-based application, so the users know where they are at all times.

We then create a mutable array and assign it to the controllers property we declared earlier. Later, when we’re ready to add rows to our table, we will add view controllers to this array, and they will show up in the table automatically. Selecting any row will automatically cause the corresponding controller’s view to be presented to the user.

www.it-ebooks.info

CHAPTER 9: Navigation Controllers and Table Views

293

TIP: Did you notice that our controllers property is declared as an NSArray, but that we’re creating an NSMutableArray? It’s perfectly acceptable to assign a subclass to a property like this. In this case, we use the mutable array in viewDidLoad to make it easier to add new controllers in an iterative fashion, but we leave the property declared as an immutable array as a message to other code that it shouldn’t be modifying this array.

The final piece of the viewDidLoad method is the call to [super viewDidLoad]. We do this because we are subclassing UITableViewController. You should always call [super viewDidLoad] when you override the viewDidLoad method, because there’s no way to know if your parent class does something important in its own viewDidLoad method.

The tableView:numberOfRowsInSection: method here is identical to ones you’ve seen before. It simply returns the count from our array of controllers. The tableView:cellForRowAtIndexPath: method is also very similar to ones we’ve written in the past. It gets a dequeued cell, or creates a new one if none exists, and then grabs the controller object from the array corresponding to the row being asked about. It then sets the cell’s textLabel and image properties using the title and rowImage from that controller. Note that in this case, since we are using one of UITableViewCell’s built-in styles instead of laying out a subclass of our own in a nib file, we have no nib file to register with the table view, and therefore can’t rely on the dequeue... method returning anything. So, we need to include the check for nil and the resulting cell-creation code, as you’ve seen before.

Notice that we are assuming the object retrieved from the array is an instance of BIDSecondLevelViewController and are assigning the controller’s rowImage property to a UIImage. This step will make more sense when we declare and add the first concrete second-level controller to the array.

The last method we added is the most important one here, and it’s the only functionality that’s truly new. You’ve seen the tableView:didSelectRowAtIndexPath: method before—it’s the one that is called after a user taps a row. If tapping a row needs to trigger a drill-down, this is how we do it. First, we get the row from indexPath.

NSUInteger row = [indexPath row];

Next, we grab the correct controller from our array that corresponds to that row.

BIDSecondLevelViewController *nextController = [self.controllers objectAtIndex:row];

Then we use our navigationController property, which points to our application’s navigation controller, to push the next controller—the one we pulled from our array— onto the navigation controller’s stack.

[self.navigationController pushViewController:nextController animated:YES];

That’s really all there is to it. Each controller in the hierarchy needs to know only about its children. When a row is selected, the active controller is responsible for getting or creating a new subcontroller, setting its properties if necessary (it’s not necessary here),

www.it-ebooks.info

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