Friday, April 16, 2010

Retrofitting iPad UISplitViewControllers

Here's a technique I developed while adding iPad support to our existing iPhone app. The view hierarchy is defined in a bog-standard iPhone XIB file, with a UITabBarController at its root. Each tab bar has a UINavigationController, which in turn holds a UIViewController subclass.

Creating a new XIB for the iPad and adding UISplitViewControllers to each tab was too much for my lazy bones, so I decided to do it programmatically at runtime, in my app delegate. Here's what the code looks like:
// Masterful retrofit hack: Wrap the view controllers inside split view controllers
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  NSMutableArray *controllers = [NSMutableArray arrayWithCapacity:[tabBarController.viewControllers count]];

  for (UIViewController *controller in tabBarController.viewControllers) {
    UISplitViewController *split = [[[UISplitViewController alloc] init] autorelease];
split.tabBarItem = controller.tabBarItem;
    DetailViewController *detail = [[[DetailViewController alloc] init] autorelease]; // a detail view will come here
    UINavigationController *nav = [[[UINavigationController alloc] initWithRootViewController:detail] autorelease];
    split.viewControllers = [NSArray arrayWithObjects:controller, nav, nil];
    [controllers addObject:split];

  tabBarController.viewControllers = controllers;
The code is iterating over the tab bar's controllers and inserting a split view into the mix.

At the other point in our code (where we would normally push views onto the navigation controllers), we detect the presence of a split view and do the appropriate thing:
if ([self.navigationController.parentViewController isKindOfClass:[UISplitViewController class]]) {
// iPad split view controller support
UISplitViewController *split = (UISplitViewController *)self.navigationController.parentViewController;
  UINavigationController *nav = [split.viewControllers objectAtIndex:1];
  DetailViewController *detail = [nav.viewControllers objectAtIndex:0];
  [detail initWithModel:phoneBookModel entryAtIndex:indexPath.row];
} else {
  UIViewController *controller = [[DetailViewController alloc] initWithModel:phoneBookModel entryAtIndex:indexPath.row];
  [self.navigationController pushViewController:controller animated:YES];
  [controller release];