Custom UISplitViewController [iOS]

Remember how in the 90′s all websites made use of the, now deprecated and long forgotten, HTML frames?

A lot of iPad apps use the type of view controller that Apple introduced together with the launch of the iPad, the UISplitViewController, which is a container view controller that manages two separate sections present on the screen at the same time. But with this default controller you’ll notice a few things which might not be desirable. For example, it hides the left view when it detects a portrait orientation. The documentation also makes a strong point of telling you to always add the split view controller as the root of the interface you’re working on.

Creating your own view controller to manage two panes of information can prove to be even easier than using a UISplitViewController (or one of the other custom implementation you’ll find open sourced on the web), and it’s good exercise for developing generic container controllers. With basic iOS development knowledge you should have your split view controller working in under an hour.

Add a new UIViewController to your project (include the auto generated xib file) and give it a name. In our case, this would be the MTSSplitViewController. Usually, it’s going to contain three views: the left, separator, and right view, but only two controllers for the left and right view since the separator doesn’t need a controller. Still, unlike the default UISplitViewController, you’re under full control of the appearance of the separator as well as free to remove it if you want. In our case, both the left and right controllers were Navigation Controllers, leading to the following screenshot that you can use for reference:

Set up the size of each of the three subviews and link the two left and right views to the views of the UIViewControllers that you’ve added (in our case, the two Navigation Controllers). You can customize the separator in interface builder or in the implementation viewDidLoad method. Add the appropriate IBOutlet properties in the header file, link them, and you’re already done with using interface builder!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import 
 
@interface MTSSplitViewController : UIViewController
{
    UINavigationController* masterNavigationController;
    UINavigationController* detailNavigationController;
 
    UIView* masterView;
    UIView* detailView;
    UIView* separatorView;
 
}
 
@property (nonatomic, retain) IBOutlet UINavigationController*   masterNavigationController;
@property (nonatomic, retain) IBOutlet UINavigationController*   detailNavigationController;
 
@property (nonatomic, retain) IBOutlet UIView*   masterView;
@property (nonatomic, retain) IBOutlet UIView*   detailView;
@property (nonatomic, retain) IBOutlet UIView*   separatorView;
 
- (void)addMasterController:(UIViewController*)controller animated:(BOOL)anim;
- (void)addDetailController:(UIViewController*)controller animated:(BOOL)anim;
 
@end

The implementation of adding a new view controller was, in our case, customized for the navigation controllers, but it can be adapted to any kind of view controller that you want to contain. The addDetailController is similar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- (void) viewDidAppear:(BOOL)animated
- (void)addMasterController:(UIViewController*)controller animated:(BOOL)anim
{
    if (masterNavigationController != nil)
        [masterNavigationController.view removeFromSuperview];
    [masterNavigationController release];
    masterNavigationController = nil;
 
    masterNavigationController = [[UINavigationController alloc] initWithRootViewController:controller];
    masterNavigationController.delegate = self;
 
    masterNavigationController.navigationBar.translucent = NO;
    masterNavigationController.navigationBar.opaque = YES;
    masterNavigationController.navigationBar.tintColor = [UIColor blackColor];    
 
    masterNavigationController.view.frame = masterView.bounds;
    controller.view.frame = masterNavigationController.view.bounds;
 
    [masterView addSubview:masterNavigationController.view];
}
 
- (void)addDetailController:(UIViewController *)controller animated:(BOOL)anim
{
    if (detailNavigationController != nil)
        [detailNavigationController.view removeFromSuperview];
    [detailNavigationController release];
    detailNavigationController = nil;
 
    detailNavigationController = [[UINavigationController alloc] initWithRootViewController:controller];
    detailNavigationController.delegate = self;
 
    detailNavigationController.navigationBar.translucent = NO;
    detailNavigationController.navigationBar.opaque = YES;
    detailNavigationController.navigationBar.tintColor = [UIColor blackColor];
 
    detailNavigationController.view.frame = detailView.bounds;
    controller.view.frame = detailNavigationController.view.bounds;
 
    [detailView addSubview:detailNavigationController.view];
}


In the implementation, you’ll have to forward all view controller specific events to the subcontrollers. Remember that this is a must for any type of controller that you’re going to implement. For example, one of them is viewDidAppear:

1
2
3
4
5
6
- (void) viewDidAppear:(BOOL)animated
{
	[masterNavigationController viewDidAppear:animated];
	[detailNavigationController viewDidAppear:animated];
	[super viewDidAppear:animated];
}

The complete list of events to forward is:

1
2
3
4
5
6
7
8
9
10
- (void) viewDidAppear:(BOOL)animated
- (void) viewWillAppear:(BOOL)animated
- (void) viewWillDisappear:(BOOL)animated
- (void) viewDidDisappear:(BOOL)animated
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
- (void) willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
- (void) didAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
- (void) willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration

 

You’re now ready to add your controller to your app! If you want to easily reuse it, leave the split controller class with just the methods you’ve added until now and manage your views from a different controller object. Here are a couple of screenshots from the Lürzer’s Archive iPad app, developed by Mind Treat Studios, showing the custom split view controller at work: