Abusing UIViewControllers

Jonah Williams ·

UIViewControllers are a fundamental building block of most iOS applications. Unfortunately many developers seem to use them in unintended and unsupported ways which leaves their apps vulnerable to bugs, rejections, unpredictable behavior under new iOS releases, and with controllers which are difficult to update or reuse. The core misconceptions behind this abuse of UIViewController are understandable. Apple presents UIViewControllers as a key element of an app’s navigation and interface stating “custom view controllers are the primary coordinating objects for your application’s content”. The basic application templates are defined in terms of their UIViewController behavior; “navigation-based”, “tab bar”, “split-view based”. From our first exposure to the platform developers are presented with UIViewControllers as classes for managing views and the transitions between them. Sadly those examples present a pattern which we, as iOS developers outside Apple, cannot follow or build on.

Any app which contains code like
[viewController.view addSubview:someOtherViewController.view];
or
viewController.view.bounds = CGRectMake(50, 50, 100, 200);
or which loads a nib doing the equivalent is abusing UIViewControllers. Given time this will emerge as bugs or at least pain for developers continuing to work on the app.

What? How is that wrong?


Update: This post was written for iOS 4. Since iOS 5 UIViewController has included a supported way to include child view controllers. See “Implementing a Container View Controller” in the class reference. The outdated content below still applies if those methods are not used appropriately. However there is now a reliable way for iOS developers to nest multiple UIViewController’s views not discussed here.


The core problem, and surprise for too many developers, is stated in the “View Controller Programing Guide for iOS” in “About Custom View Controllers“:

Each custom view controller object you create is responsible for managing all of the views in a single view hierarchy. In iPhone applications, the views in a view hierarchy traditionally cover the entire screen, but in iPad applications they may cover only a portion of the screen. The one-to-one correspondence between a view controller and the views in its view hierarchy is the key design consideration. You should not use multiple custom view controllers to manage different portions of the same view hierarchy. Similarly, you should not use a single custom view controller object to manage multiple screens worth of content.

Essentially the rootViewController of the current UIWindow should be the only UIViewController with a visible view.

With multiple UIViewController’s views visible at once some of those controllers may not receive important messages like -viewWillAppear: or -didReceiveMemoryWarning. Additionally some of their properties like parentViewController and interfaceOrientation may not be set or updated as expected.

Why won’t my UIViewController rotate with the device?” states this even more explicitly.

If you add an additional view controller’s UIView property to UIWindow (at the same level as your primary view controller) via the following:

[myWindow addSubview:anotherController.view];

this additional view controller will not receive rotation events and will never rotate. Only the first view controller added to UIWindow will rotate.

Now Apple is able to provide “container view controllers” like UINavigationController and UITabBarController which manage the presentation of other UIViewController’s views. Unfortunately to do so requires the use of private API calls which are not available for any app seeking approval for distribution in the App Store (discussed in more detail here: https://devforums.apple.com/message/310806#310806). We can come close but without a supported path for implementing custom container view controllers nesting UIViewControllers’ views will not give us all the behavior we expect from a UIViewController.

Isn’t close good enough?

Not really. We can implement a custom container view controller which sends messages like -viewWillAppear to its child view controllers but that only solves part of the problem.

  • Without being able to rely on interfaceOrientation those child view controllers may never support rotation correctly.
    Since view controllers’ views are expected to fill the window views “behind” modal view controllers may not be drawn or may be unloaded while visible.
  • Without properties like parentViewController or navigationController being set all of our child view controllers need to be aware of how they are being presented and the fact that they cannot rely on all the behavior they should be able to expect from inheriting from UIViewController.
  • Child view controllers may not be properly inserted into the responder chain so they may never see events passed up the chain from their views.

With no public setters available for these properties or documentation of these behaviors there’s currently no way to resolve this situation (see this thread for further discussion: https://devforums.apple.com/message/365171#365171). Since there is currently no supported means of creating container view controllers those behaviors which do work now are not guaranteed to continue to function in future iOS updates and may vary between existing iOS versions.
By nesting UIViewControllers we create a UIViewController instance which cannot rely on the entire contract offered by UIViewController. A non-obvious and context specific problem for any developer working on the app in the future.

So what’s the alternative?

The “View Controller Programming Guide for iOS” at least suggests an alternative:

Note: If you want to divide a view hierarchy into multiple subareas and manage each one separately, use generic controller objects (custom objects descending from NSObject) instead of view controller objects to manage each subarea. Then use a single view controller object to manage the generic controller objects.

A controller does not necessarily have to be a subclass of UIViewController. We can create controller objects responsible for managing specific behaviors or portions of our view and have these controllers inherit from NSObject instead of from UIViewController. This allows you to break up your app’s controller logic into manageable classes with clear responsibilities while still following the limitations of UIViewController. This works well when you want to have different controllers manage different sections of a single view hierarchy.


@interface SampleController : NSObject {
}
@end

This is however an inadequate replacement for custom container view controllers which should be able to manage transitions between arbitrary UIViewControllers. Instead we have to either reinvent much of UIViewController’s behavior (-viewWillAppear: and -viewDidDisappear: style lifecycle methods, unloading and reloading of views on demand, transformation of views in response to device rotations) or abuse UIViewController and risk producing unstable apps.


@protocol ViewController
- (void)viewDidAppear:(BOOL)animated;
- (void)viewDidDisappear:(BOOL)animated;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewWillDisappear:(BOOL)animated;
-(UIView *)view;
@end

@interface SampleController : NSObject {
}
@end

@interface SampleContainerViewController : UIViewController {
}
– (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated;
@property(nonatomic, copy) NSArray *viewControllers;
@end

For now I have found that the best solution is to stay within the supported constraints of UIViewController and build custom non-UIViewController controllers as necessary. Have you found a better alternative, chosen to use UIViewControllers anyway, or avoided this problem entirely?