- Developing with Navigation Controllers and Split Views
- Recipe: Building a Simple Two-Item Menu
- Recipe: Adding a Segmented Control
- Recipe: Navigating Between View Controllers
- Recipe: Presenting a Custom Modal Information View
- Recipe: Page View Controllers
- Recipe: Scrubbing Pages in a Page View Controller
- Recipe: Tab Bars
- Recipe: Remembering Tab State
- Recipe: Building Split View Controllers
- Recipe: Creating Universal Split View - Navigation Apps
- Recipe: Custom Containers and Segues
- One More Thing: Interface Builder and Tab Bar Controllers
- Summary
Recipe: Building Split View Controllers
Split view controllers provide the preferred way to present hierarchically-driven navigation on the iPad. They generally consist of a table of contents on the left and a detail view on the right, although the class (and Apple's guidelines) are not limited to this presentation style. The heart of the class consists of the notion of an organizing section and a presentation section, both of which can appear onscreen at once in landscape orientation, and whose organizing section converts to a bar button-launched popover in portrait orientation.
Figure 5-7 shows the very basic split view controller built by Recipe 5-8 in landscape and portrait orientations. This controller adjusts the brightness of the detail view by selecting an item from the list in the root view. In landscape, both views are shown at once. In portrait orientation, the user must tap the upper-left button on the detail view to access the root view in a popover.
Figure 7 (Click to Enlarge) At their simplest, Split View Controllers consist of an organizing pane and a detail view pane. The organizing pane, which is hidden in portrait orientation, can be viewed from a popover accessed from the navigation bar.
Accomplishing this requires three separate objects: the root and detail view controllers, and building the split view controller. What's more, you'll generally want to add the root and detail controllers to navigation controller shells, to provide a consistent interface as well as. In the case of the detail controller this provides a home for the bar button in portrait orientation. The following method builds the two child views, embeds them into navigation controllers, adds them to a view controller array, and returns a new split view controller that hosts those views.
- (UISplitViewController *) splitviewController { // Create the navigation-run root view ColorViewController *rootVC = [ColorViewController controller]; UINavigationController *rootNav = [[UINavigationController alloc] initWithRootViewController:rootVC]; // Create the navigation-run detail view DetailViewController *detailVC = [DetailViewController controller]; UINavigationController *detailNav = [[UINavigationController alloc] initWithRootViewController:detailVC]; // Add both to the split view controller UISplitViewController *svc = [[UISplitViewController alloc] init]; svc.viewControllers = [NSArray arrayWithObjects: rootNav, detailNav, nil]; svc.delegate = detailVC; return svc; }
The root view controller is typically some kind of table view controller, as is the one in Recipe 5-8. Tables view controllers are discussed in great detail in Chapter 11, but what you see here is pretty much as bare bones as they get. It is a list of 10 items, each of whose cell titles are tinted proportionally between 0% and 90% of pure white.
When an item is selected, the controller uses its built in splitViewController property to affect its detail view. This property returns the split view controller that owns the root view. From there, the controller can retrieve the split view's delegate, which has been assigned to the detail view. By casting that delegate to the detail view controller's class, the root view can affect the detail view more meaningfully. In this extremely simple example, the selected cell's text tint is applied to the detail view's background color.
Recipe 5-9's DetailViewController class is about as skeletal an implementation as you can get. It provides the most basic functionality you need in order to provide a detail view implementation with split view controllers. This consists of the will hide / will show method pair that adds and hides that all-important bar button for the detail view.
When the split view controller converts the root view controller into a popover controller in portrait orientation, it passes that new controller to the detail view controller. It is the detail controller's job to retain and handle that popover until the interface returns to landscape orientation. In this skeletal class definition, a retained property holds onto the popover for the duration of portrait interaction.
Recipe 5-9 Building detail and root views for a split view controller
@interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate> { UIPopoverController *popoverController; } @property (nonatomic, retain) UIPopoverController *popoverController; @end @implementation DetailViewController @synthesize popoverController; + (id) controller { DetailViewController *controller = [[DetailViewController alloc] init]; controller.view.backgroundColor = [UIColor blackColor]; return controller; } // Called upon going into portrait mode, hiding the normal table view - (void)splitViewController: (UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController: (UIPopoverController*)aPopoverController { barButtonItem.title = aViewController.title; self.navigationItem.leftBarButtonItem = barButtonItem; self.popoverController = aPopoverController; } // Called upon going into landscape mode. - (void)splitViewController: (UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem { self.navigationItem.leftBarButtonItem = nil; self.popoverController = nil; } - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { return YES; } @end @interface ColorViewController : UITableViewController @end @implementation ColorViewController + (id) controller { ColorViewController *controller = [ [ColorViewController alloc] init]; controller.title = @"Colors"; return controller; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 10; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"generic"]; if (!cell) cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier:@"generic"]; cell.textLabel.text = @"Brightness"; cell.textLabel.textColor = [UIColor colorWithWhite:(indexPath.row / 10.0f) alpha:1.0f]; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // On selection, update the main view background color UIViewController *controller = (UIViewController *)self.splitViewController.delegate; UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; controller.view.backgroundColor = cell.textLabel.textColor; } @end