Building a custom UIViewController

Guides | Tutorial By 5 years ago

iOS apps are all based around view controllers when it comes to different sections and screens, some of the most common being:

– Your standard view controller, all other view controllers subclass this one, and more complex view controllers use these as children.

– Another common view controller, responsible to controlling a stack of UIViewControllers and pushing or popping them with a nice slide animation. This is the view controller in effect when you see the pointy back button up the top left of the screen.

– A view controller that has an array of tabs and a UIViewController per tab. Only shows the view controller for selected tab.

– This controller takes two UIViewControllers, one for the left menu and one for the main content area. This one is iPad only and will automatically show/hide the menu UIViewController based on device orientation.

– Another iPad only view controller, this one shows a UIViewController in a popup on top of all other views. Pre-iOS5 this was used by the UISplitViewController to show the left menu when in portrait mode.

The above view controllers can all be mixed and matched (with a couple of exceptions). You can embed a UINavigationViewController inside a UITabBarViewController, a UITabBarViewController inside a UISplitViewController, and so on and so forth.

These standard view controllers will do fine for most projects, but when you decide you want to get a little bit fancy you might need to build your own custom UIViewController. A good example of this is the Path or Facebook app, with the slide to reveal side menu.

When building a custom UIViewController, I recommend against adding ‘uncontrolled’ views to your controller’s view, and instead invest a little extra time taking UIViewControllers and adding their ‘controlled’ views to your ‘controlled’ view, and then forward all appearance and rotation messages to your child view controllers. Doing so will ensure that if you ever decide to scrap the parent view controller or make vast amount of changes to it, your code will be isolated into your UIViewControllers.

In iOS5 the UIViewController will automatically forward appearance and rotation messages to it’s children. There are some advantages and disadvantages to this, the most obvious being that if you plan to support only iOS5+ then you’ve got it pretty easy. Personally I think iOS4 is great and I don’t think I will ever drop support for it, at least not for a long time. If so then you need to manually forward those messages, and I recommend disabling the automatic ones so you don’t double up on iOS5+. Another issue is the complexity of the custom UIViewController. If you simply just want two child controllers on the screen 100% of the time then forwarding messages is quite easy. If you’re doing something a bit more tricky, such as Path/Facebook’s side menu then you need to be picky about when and what you tell the children because even though the children will have parents all of the time, they may not be on screen so an appearance or rotation method may not be appropriate at certain times.

That’s enough rambling on, time for some code. This UIViewController will contain a top controller and a bottom controller, and I will manually forward the appearance and rotation messages. I won’t explain how to actually add this to the screen, which you should probably have a good knowledge of if you’re undertaking this tutorial, but I will provide sample code at the end.

Firstly I design my header file, which in this case is extremely simple and just has two retained properties for a top and bottom controller.

@interface TopBottomViewController : UIViewController

@property (nonatomic, assign) UIViewController* topViewController;
@property (nonatomic, assign) UIViewController* bottomViewController;


Now back in the implementation, the first thing I do is opt out of auto method forwarding for appearance and rotation messages.

- (BOOL) automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
	return NO;

Synthesize your top and bottom properties.

@synthesize topViewController;
@synthesize bottomViewController;

Now I’ve written a single method to handle both view setups for the top and bottom controllers, which uses references so I don’t need to rewrite anything, then I’ve overridden my set methods.

- (void) setTopViewController:(UIViewController *)_topViewController
	[self setViewController:_topViewController isTop:YES];

- (void) setBottomViewController:(UIViewController *)_bottomViewController
	[self setViewController:_bottomViewController isTop:NO];

- (void) setViewController:(UIViewController*) viewController isTop:(BOOL) isTop
	UIViewController** viewControllerRef = ((isTop) ? &topViewController : &bottomViewController);

	if(*viewControllerRef == viewController)

	[(*viewControllerRef).view removeFromSuperview];

	[viewController retain];
	[*viewControllerRef release];
	*viewControllerRef = viewController;

	const CGSize selfSize = self.view.frame.size;

	CGRect viewFrame = CGRectMake(0, 0, selfSize.width, selfSize.height / 2);

		viewFrame.origin.y += viewFrame.size.height;

	UIView* viewControllerView = viewController.view;
	[viewControllerView setFrame:viewFrame];
	[viewControllerView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | ((isTop) ? UIViewAutoresizingFlexibleBottomMargin : UIViewAutoresizingFlexibleTopMargin))];
	[self.view addSubview:viewControllerView];

Run the project and it will appear to work perfectly. Now the final step is the manual method forwarding so it works fine going from screen to screen.

- (void) viewWillAppear:(BOOL)animated
	[super viewWillAppear:animated];

	[topViewController viewWillAppear:animated];
	[bottomViewController viewWillAppear:animated];

- (void) viewDidAppear:(BOOL)animated
	[super viewDidAppear:animated];

	[topViewController viewDidAppear:animated];
	[bottomViewController viewDidAppear:animated];

- (void) viewWillDisappear:(BOOL)animated
	[super viewWillDisappear:animated];

	[topViewController viewWillDisappear:animated];
	[bottomViewController viewWillDisappear:animated];

- (void) viewDidDisappear:(BOOL)animated
	[super viewDidDisappear:animated];

	[topViewController viewDidDisappear:animated];
	[bottomViewController viewDidDisappear:animated];

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
	[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];

	[topViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
	[bottomViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
	[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];

	[topViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
	[bottomViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
	[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];

	[topViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
	[bottomViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];

Ouch, lots of code, but that’s it, now you have something like this: (the different colours distinguish separate view controllers)

Download the sample code here.

And after some work: