Introduction
This project implements a split view controller for iPad. It is a lot like the UISplitViewController class in the iOS library, but it is more versatile
and easy to use. In this article, we will refer to the split view controller being developed as simply the split view controller and the one in the iOS library
will be referred to as UISplitViewController. The split view controller is compatible with iOS 4.2 and up.
The split view controller displays two view controllers next to each other: a side view controller on the left side of the screen,
and a main view controller displayed slightly larger on the right side. It supports all the user interface orientations that are supported by these two view controllers.
Throughout this article, we will refer to the side view controller and the main view controller collectively as the sub-view controllers.
Here are some of the differences between this split view controller and the UISplitViewController that is part of the iOS library:
- The split view controller has a flag that allows the client to choose if the side view controller must be hidden when iPad is in
portrait orientation.
UISplitViewController always hides the side view controller in this orientation.
- Unlike
UISplitViewController, the split view controller does not have to be the root controller of the app. A UISplitViewController cannot be pushed
onto a UINavigationController's stack and cannot be presented modally. The split view controller does not impose these restrictions.
- The split view controller's list button can be displayed in a tool bar or a navigation controller's navigation bar. We call it the list button since it displays
the side view controller's view in a popover view controller, and the side view controller is typically a
UITableViewController that shows a list
of items (this need not be the case; the side view controller can be an arbitrary UIViewController object).
- The split view controller can easily be set up programmatically. In fact, that is how we use it in the example project
in the source code.
The screenshot below shows what the split view controller looks like in a UINavigationController in a landscape orientation
in the example project:

Here is what it looks like in portrait orientation, after pressing the list button:

Implementation
The split view controller is implemented by the class SplitViewController, which is a sub-class of the UIViewController class.
SplitViewController initializes from a XIB file, via the initWithNibName:bundle: method. The standard XIB file,
SplitView.xib, which is shown in the image below, contains a root UIView, with a single sub-view. We call this sub-view the view splitter;
it is a black vertical line (specifically, it's a plain UIView with a black background, a width of 1, and a height equal to the height of its parent view)
whose purpose is to separate the views of the side view controller and the main view controller.

The x-location of the view splitter defines the (vertical) split point of the sub-view controllers, although the client of the split view controller can override
this split point through the splitPoint property. For aesthetic motivations, the x-location of the view splitter in SplitView.xib has been chosen
so that the golden ratio applies when the split view controller occupies all of iPad's screen
in landscape orientation (1004/620 = 1.61935...; 620/384 = 1.61458...). The split view controller basically works as follows: when the side view controller and the main
view controller are assigned to the split view controller, the split view controller adds the views of those view controllers as sub-views of its main-view, using the
split view's frame to properly set the locations and dimensions of the view controller's views. When the interface orientation is rotated, the frames of the views
of the sub-view controllers are adjusted accordingly with a nice animation.
The client chooses whether the side view controller should be hidden in portrait orientation by the setting the value of the boolean
hideSideViewControllerInPortraitOrientation property (whose default value is NO). If this property is set to YES and the orientation changes
from landscape to portrait, the side view controller's view is removed from the split view controller's view and a list button is added to the left of the navigation
controller's navigation bar (if there is one). The text used for the list button is the value of the listButtonTitle property.
There is a problem that needs to be dealt with here when iOS 4.2 or iOS 4.3 has to be supported: in these versions of iOS, UINavigationItem
can only have one left button. That implies that we cannot add the list button if there already is a back button or some other left-hand-side button.
The workaround that SplitViewController makes provision for is to display a single button on the left that looks like two buttons.
This is a bit of a hack'esque solution, but it works. The button is a UIBarButtonItem with a custom view that displays a UIImage.
When the button is pressed, we determine which of the two buttons were really pressed by inspecting the location of the touch. In order to have
a UITouch object at hand to get a location from, we implement the class MyImageView, which is a sub-class of UIImageView.
This class simply overrides the touchesEnded:withEvent: method of UIView, where it calls a selector of the SplitViewController
with the set of UITouch objects as a parameter.
We have described how SplitViewController works when it is one of the view controllers in the stack of a UINavigationViewController.
This is not the only way SplitViewController can be used. It can be used without a bar at the top. This makes sense when the split view controller
does not hide the side view (that is, if hideSideViewControllerInPortraitOrientation is set to NO), but if it does hide the side view,
the list button needs to be displayed somewhere. The alternative is to use SplitViewController with a UIToolbar. In this case, you will
have to create a custom XIB file that contains a UIToolbar with a UIToolbarButtonItem that will be used for the list button.
The file ToolbarSplitView.xib in the example code is an example of such
a custom XIB file. You must link up the toolbar and toolbarListButton IBOutlets to the UIToolbar
and the UIToolbarButtonItem in the XIB, respectively. The reason why SplitViewController needs a reference to the UIToolbar
is so that, when the list button is pressed, it can compute the location of the arrow of the popover controller, so that the arrow appears directly beneath the list button.
Of course, when the split view controller is in landscape orientation, the list button needs to be hidden. SplitViewController hides the list button
by setting its width to 0.1. When the list button needs to become visible again, its width is reset to its original value.
Using the code
The source files that need to be added to an Xcode project in order to use the SplitViewController are those
in the SplitViewControllerTestProject/SplitViewController folder in the zip file (Download source code - 54.8 KB).
The Xcode project in the zip file attempts to demonstrate all the ways in which SplitViewController can be used, which I briefly explain in the following paragraphs.
- To make the
SplitViewController the root controller of an app, one only needs to make a few small changes in the app's delegate class (the class that implements
the UIApplicationDelegate protocol). We will add the UIView of the SplitViewController to the UIWindow of the app,
so the UIView will be retained by the UIWindow. But that will not ensure that the SplitViewController is retained,
so we will need to make sure that the SplitViewController is not deallocated while the app runs. We may want to add a reference to the SplitViewController
in the delegate class, so that we can eventually release it as soon as the application delegate is deallocated:
@interface AppDelegate : UIResponder <UIApplicationDelegate>
{
SplitViewController* topViewController;
}
And in the dealloc method:
- (void)dealloc
{
[topViewController release];
[_window release];
[super dealloc];
}
Finally, we create and show the SplitViewController in the application:didFinishLaunchingWithOptions: method like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.backgroundColor = [UIColor whiteColor];
topViewController = [[SplitViewController alloc] init];
UIViewController* mainViewController = topViewController.mainViewController = mainViewController;
UIViewController* sideViewController = topViewController.sideViewController = sideViewController;
[sideViewController release];
[mainViewController release];
CGRect initialFrame = topViewController.view.frame;
initialFrame.origin.y+=20;
topViewController.view.frame = initialFrame;
[self.window addSubview:topViewController.view];
[self.window makeKeyAndVisible];
return YES;
}
Note that when the device orientation changes, SplitViewController first asks the side- and main view controllers if it should auto-rotate
to that new interface orientation. It will return YES for the auto-rotation only when both sub-view controllers return YES.
Therefore, make sure that in the shouldAutorotateToInterfaceOrientation: method of both sub-view controllers, return YES
for every interface orientation that you want to support.
How you make the side view controller and main view controller talk to each other is up to you. It is easy to obtain a reference to SplitViewController
in the side- or main view controller - just add a property with type SplitViewController* and with the name splitViewController as in:
@interface MySideViewController : UITableViewController
{
SplitViewController* splitViewController;
}
@property (nonatomic, assign) SplitViewController* splitViewController;
@end
The setMainViewController: or setSideViewController: method of SplitViewController will find this property and assign itself to it.
Here is the code to push a SplitViewController, which hides its side view controller in portrait orientation, on a navigation controller's stack:
SplitViewController* nextSplitViewController = [[SplitViewController alloc] init];
nextSplitViewController.title = @"Split view controller's title text";
nextSplitViewController.hideSideViewControllerInPortraitOrientation = YES;
nextSplitViewController.listButtonTitle = @"Items";
nextSplitViewController.sideViewControllerHiddenLeftButtonsImage = [UIImage imageNamed:@"splitter_items.png"];
UIViewController* mainViewController = nextSplitViewController.mainViewController = mainViewController;
UIViewController* sideViewController = nextSplitViewController.sideViewController = sideViewController;
sideViewController.contentSizeForViewInPopover = CGSizeMake(320,320);
[mainViewController release];
[sideViewController release];
[myTopViewController.navigationController pushViewController:nextSplitViewController animated:YES];
[nextSplitViewController release];
The sideViewControllerHiddenLeftButtonsImage property only needs to be set if the app will run on iOS 4.2 or iOS 4.3.
It is the image that simulates two buttons when the side view controller is hidden and the list button needs to be displayed, like this image:

To make the image, run the app on a device with iOS 5.0+ (or on the simulator with iOS 5.0+), make a screenshot, and cut out the buttons.
To have the list button displayed in a UIToolbar, we must create our own XIB to use with the SplitViewController.
See the file ToolbarSplitView.xib in the example code for an example.
We create an iPad XIB, add a split view (it is a vertical line; a plain UIView with a black background and a width of 1), and add
a UIToolbar containing a UIToolbarButton (we can add many other UIBarItems to the UIToolbar if desirable).
Set the class of the File's Owner to SplitViewController and connect the viewSplitter, toolbar,
and toolbarListButton IBOutlets. If we want to bind the actions of any UIToolbarButton in the UIToolbar,
other than the list button, we will have to do this programmatically, as in the example below.
SplitViewController* nextSplitViewController =
[[SplitViewController alloc] initWithNibName:@"MyToolbarSplitView" bundle:nil];
nextSplitViewController.title = @"Split view controller's title text";
nextSplitViewController.hideSideViewControllerInPortraitOrientation = YES;
UIViewController* mainViewController = nextSplitViewController.mainViewController = mainViewController;
UIViewController* sideViewController = nextSplitViewController.sideViewController = sideViewController;
sideViewController.contentSizeForViewInPopover = CGSizeMake(320,320);
[mainViewController release];
[sideViewController release];
for (UIBarButtonItem* nextItem in ((UIToolbar*) [nextSplitViewController.view viewWithTag:15]).items)
{
if (nextItem.tag==15)
{
nextItem.target = self;
nextItem.action = @selector(dismissModalView);
}
else if (nextItem.tag==16)
{
nextItem.target = mainViewController;
nextItem.action = @selector(sayHi);
}
}
[myTopViewController presentModalViewController:nextSplitViewController animated:YES];
[nextSplitViewController release];
In that example, MyToolbarSplitView is the name of the custom XIB file that we created.
History