Saturday, May 28, 2011

Dynamic Tabs for the iPad

The UISplitViewController allows us to present a master/detail view for iPad apps. For an app with a complicated data model or many detail views, it can be useful to have the detail view use a UITabBarController as the container. In this post, I'll show you how to open multiple tabs in the detail view dynamically. In addition to the dynamic tabs, I'll also show you a few Interface Builder tricks that will cut your development time.

Each of the detail views will be contained in a UINavigationController. Using some IB tricks, we can put each detail view in it's own xib. As you know if you've read any of my previous posts, I'm a big fan of Interface Builder, and I think you should leverage it wherever possible. In this case we'll be using it wire almost the entire UI, with very little external code needed.

Go to GitHub and clone our repository. If you're using Xcode 4, look in the workspaces directory and open the ikhoyo-public workspace.

There are two projects in the workspace. One is called ikhoyo-top. It contains a 'kitchen sink' app showing all the techniques described on this blog. The other project is called ikhoyo-ui. It's a static library containing the UI classes that you can incorporate into your own projects or workspace.

The classes we'll be using are:
  • IkhoyoDynamicTabBarController - the detail view container. DetailViewController inherits from this class.
  • IkhoyoDynamicTabViewController - the base class for all the dynamic views. Each of the individual detail views inherit from this class. 
Click on SampleWebView.xib to see the detail view we'll be working with. It's basically a UINavigationController that contains an IkhoyoWebViewController. (I'll be showing you what an Ikhoyo Web View is in a future post). This xib may look a little funny, because there is still a view in here at the same level as the UINavigationController. This is the first Interface Builder trick I want to show you. If you attempt to instantiate this xib without the view, you'll get an error because the view cannot be located - even though the view is never used! You still need it connected to File's Owner so that the xib loading code will work. But it will be ignored since we'll be getting the UINavigationController to set as our root controller.

Open the Connections Inspector for File's Owner. File's Owner is SampleWebView, which is a subclass of IkhoyoDynamicTabViewController. The way we determine the root view controller in the xib file is by wiring it up to the root outlet. In this case, root is wired up the the UINavigationController. This is the controller we'll be dynamically adding to the DetailViewController's tab bar.

Now look in IkhoyoDynamicTabBarController. The addTab method is called to load the dynamic tab view. In this case, it will load the SampleWebView. Let's look at the code:

IkhoyoDynamicTabViewController* ctlr = [self hasTab:name];
if (!ctlr) {
    Class cls = NSClassFromString(c);
    ctlr = [[[cls alloc] initWithNibName:c bundle:nil] autorelease];
    ctlr.name = name;
    [ctlr view]; // Needed to force wiring in some cases
    ctlr.root.tabBarItem.title = name;
    if (image)
        ctlr.root.tabBarItem.image = image;
    NSMutableArray * vcs = [NSMutableArray arrayWithArray:
      [self viewControllers]];
    [tabs addObject:ctlr];
    [vcs addObject:ctlr.root];
    [self setViewControllers:vcs animated:NO];
}
[self setSelectedViewController:ctlr.root];
[ctlr start:param];
return ctlr;

The c variable contains the class name (SampleWebView). We use this to instantiate the xib. Basically, we instantiate the xib, get the root controller (which we wired up above), and add it to the tab bar's view controllers (via setViewControllers). You can also pass in the name and image that will appear in the tab bar item. Notice the [ctlr view] statement. This is related to the Interface Builder trick I described above. When we load the controller from the xib, all the wiring is not complete until the view in the xib is referenced. That's why we needed to put the view there, even though it's not used.

The addTab method is triggered from IkhoyoHomeTableViewController (in the ikhoyo-top project). To see it fire, put a breakpoint on the didSelectRowAtIndexPath method. If you start the app in the simulator and click on 'Sample Web View' in the master table view on the left, you can trace through and see how the addTab method works.

After the tab is loaded, the start method is called to do any view specific configuration. In our case, it starts the SampleWebView.

I've used Ikhoyo dynamic tab bars in several iPad apps that I've written. They're a very powerful technique to have in your toolbox for apps that have many detail views. We also learned some nice Interface Builder tricks that allow us to use IB in non-standard ways. In this case, we saw how we can make our code a bit more modular by having a UINavigationController based view residing in it's own xib file.

In the next post, I'll describe what an Ikhoyo Web View is, and why it's another powerful technique to have in our arsenal.

No comments:

Post a Comment