Abusing UIViewControllers

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?

This entry was posted in Mobile and tagged , , . Bookmark the permalink.

71 Responses to Abusing UIViewControllers

  1. Jason says:

    It’s even worse than this. When I found a bug in 4.0 concerning the behavior of modal view controllers presenting other modals, the Apple engineer implied even that was not recommended or supported.

    The funny thing about the “abuse” you are talking about in your post is that I got the idea from Apple’s own Page Control sample code. I still have no idea how to use IB with non-UIViewController controllers.

    • Anton says:

      Is it difficult to integrate non-UIViewController controllers with IB? I haven’t tried yet. Is there some trick to it, or is it as simple as setting the class of “File’s Owner” to the class of my non-UIViewController controller?

      • Jonah says:

        There’s no trick. Just set the File’s Owner to whichever class you want to bind outlets and actions to and load the nib using [[NSBundle mainBundle] loadNibNamed:@"myNib" owner:myController options:nil] since -initWithNibName:bundle: is only available on UIViewController. The Resource Programming Guide covers the details of the nib loading process if you need a reference.

  2. Anton says:

    Thanks, Jonah! I’ll try it. :)

    • Anton says:

      In your code above, wouldn’t your sampleController conform to the “ViewController” protocol like this?

      ViewController.h

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

      SampleController.h

      @interface SampleController : NSObject {
      UIView *_view;
      }
      @property (nonatomic, retain) UIView *_view;
      @end

      SampleController.m

      #import "SampleController.h"

      @implementation SampleController

      @synthesize _view;

      - (void)viewDidAppear:(BOOL)animated {...}
      - (void)viewDidDisappear:(BOOL)animated{...}
      - (void)viewWillAppear:(BOOL)animated{...}
      - (void)viewWillDisappear:(BOOL)animated{...}

      -(UIView *)view {
      return _view;
      }

      SampleContainerViewController.h

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

  3. Anton says:

    Oops, the protocol code didn’t take because it was in brackets:

    SampleController.h

    @interface SampleController : NSObject <ViewController> {
    UIView *_view;
    }
    @property (nonatomic, retain) UIView *_view;
    @end

  4. Anton says:

    grrrr…. :(

    How do I get -(void)viewDidLoad to get called on my sub-view controller? Am I supposed to call it myself?

    • Anton says:

      Wait, so I have to call all of the -viewWillAppear, -viewDidAppear, etc. view lifecycle methods manually? Is this really the best way?

      • Jonah says:

        That’s right. If you’re building a custom container view controller it will be up to you to notify the controllers of any subviews of lifecycle events. Due to the assumptions made by UIViewController and UIKit nothing will reliably send those messages for you if you nest UIViewControllers’ views.

        Alternately you could design those subview controllers to not care about events like `-viewWillAppear:` and instead update whenever their backing model changes. However I often find that I want to reinvent the view loading/unloading behavior of UIViewController or at least change behavior when their view is not visible so I use some variant of the protocol shown above.

      • Anton says:

        Okay. I think I understand.

        So when an event, like an orientation event, gets caught by the containingViewController, it iterates through its array of subViewControllers and calls the appropriate method on each one. Is that the idea?

        In that case, you might want other methods included in your protocol, right?

  5. Jonah says:

    Exactly right Anton. Depending on what your UIViewController does with its subviews you might need to expand that protocol or discard it entirely. For example if your subviews are always visible and you don’t need your sub-view controllers to change their views’ layout when they rotate then you don’t need such a protocol at all. For example you might have controller classes which act as a UITableView’s delegate and datasource but don’t need any UIViewController style lifecycle events. Alternately if you are building a carousel display of subviews you might need to have a controller for each sub-view which can load and unload the view, respond to rotations, be notified when then view appears and disappears, and even change its view’s behavior when it is not the foremost view. In that case you might those controllers to all adopt a more complex protocol.

  6. Andrew Szczepanski says:

    Great post. I was going through the same frustrations and was constantly asking myself (and googling) if there is a better way. You just confirmed that there isn’t, so now I can stop thinking about it. Thanks!

  7. Jason Elder says:

    I was confused by this at first because the sample PageControl app from Apple uses the very technique you advise against:

    line 140 in PhoneContentController.m: [scrollView addSubview:controller.view];

    but then I realised that PhoneContentController is a subclass of ContentController, which is a subclass of NSObject, and they’ve overridden – view.

    In my app I’m employing something similar to PageControl, but the scrollview containing class (analogous to PhoneContentController) has to be a UIViewController, as I’m pushing it onto the navigation stack (with pushViewController: )from a tableview didSelectRowAtIndexPath: method.

    Of course this means that, due to your explanation above, viewWillAppear: et al don’t get triggered on the subviews.
    I cheat by by calling [self performSelector:@selector(viewWillAppear: )]; on the viewDidLoad method of the subview, which is fine for the first load, but any subsequent reappearance of the view doesn’t trigger viewWillAppear:

    I would like to follow best practices, and certainly not anger the Apple App Store gods. Is there a better way to do this given the requirement that the ScrollView containing controller needs to be pushed onto a navigation stack?

    • Anton Thorn says:

      I may not have followed you all the way, but would it be feasible for your scrollView (which is your containing viewController, right?) to call -viewWillAppear on the subView itself, right before it calls -addSubView?

      scrollView.m:

      [subViewController viewWillAppear];
      [self addSubView:subViewController.view];
      [subViewController viewDidAppear];

      I ended up doing something similar, maybe even identical, to that.

      • Jason Elder says:

        Hi Anton,
        That would solve having to call
        [self performSelector:@selector(viewWillAppear: )];
        on the viewDidLoad method of the subview, and would be cleaner, so I think I’ll adopt it, cheers! ;)

        The real problem I have though is that each of the subviews viewWillAppear: methods don’t get triggered when they scroll back into view. Looking around I discovered this blog post which explained why, and also that it is bad practice, so I’m looking at refactoring.
        Also, looking at Jonah’s reply I’ve realised I might have my definitions wrong, so I’m going to carry this on below…

    • Jonah says:

      If I understand your app’s structure correctly I don’t see a problem with what you are doing.

      If you have a UINavigationController at the root of your view controller hierarchy then adding a custom UIViewController subclass to its navigation stack is fine, that’s what UINavigationController is for after all. This custom UIViewController could then have a UIScrollView as its root view. Subviews of that UIScrollView should not have their own UIViewController controller classes but may have controllers which inherit from NSObject.
      Each “page” should not have a UIViewController but the scroll view containing those pages probably can.

      This roughly matches the structure seen in the PageControl sample. When that example is run on an iPhone “MyViewController” (a UIViewController subclass) is the root view controller. That controller’s root view contains pages, each of which is managed by a “PhoneContentController” (which is not a UIViewController).

      • Jason Elder says:

        Hi Jonah,
        What you describe is exactly my situation. From the root of the nav heirarchy:
        UINavigationController->UITableViewController->UIViewController (controls the paging ScrollView)->UIViewController(one for each page view)

        In the PageControl sample, the paging ScrollView controller is an NSObject, so I thought to modify my code to implement that, but I can’t do that because of the navigation stack push. I didn’t think to reverse the order so that the NSObject subclass was at the top.

        Doing that would make the app more robust, but from what I understand I would have to set up the view lifecycle protocol methods and pass viewWillAppear: messages explicitly from the paging ScrollView controller to the NSObject-subclassed subviews.
        Is that correct?

        As an aside, I always thought that the root view controller referred to the view controller at the root (in this case the UINavigationController). I didn’t realise it was the top one – seems like a weird naming convention.

  8. Jonah says:

    Jason, let me try to clarify the hierarchy I’m picturing as I’m afraid I have confused you.

    UINavigationController
    ....|- UITableViewController
    ....|- UIViewController (containing a UIScrollView)
    ......|- PageControllers (non-UIViewController classes responsible for managing views on a single page within the UIScrollView)

    “rootViewController” is unfortunately an overloaded term. When I referred to the root of the view controller hierarchy I meant the UINavigationController in this example which would should find as your application’s UIWindow’s “rootViewController” property. That navigation controller also has a “root view controller” as seen in “-initWithRootViewController:” referring to the “bottom” view controller on the navigation stack.

    When I referred to a controller’s “root view” I mean the UIView exposed as a UIViewController’s “view” property.

    I hope that helps.

    • Jason Elder says:

      Ah thanks, that makes more sense to me – or rather it’s more in line with what I initially thought.
      The disconnect happened because I didn’t realise that in the PageControl sample, the top level controller (MyViewController) is by default the root view controller, because it is the only view controller.
      So to bug you one more time, if I converted my top level view controllers (PageController in your diagram above) and implemented viewWillAppear: methods for them, is it correct that I would have to trigger those methods explicitly at an appropriate place in the UIScrollView’s controller whenever the subviews did appear?

      • Anton Thorn says:

        Hi Jason,

        I’m pretty sure you’ll have to trigger those methods explicitly every time they appear, and conversely you’d have to trigger the -viewWillDisappear and -viewDidDisappear methods if you were using them.

        Jonah’s the expert, though, so wait for him to say so. ;)

      • Jonah says:

        You and Anton have it right.

        A controller class which is not a UIViewController isn’t going to have any mechanism for automatically calling -viewWillAppear: or other UIViewController lifecycle methods. How could it? You’re working with an object which is not a UIViewController so you can’t expect it to act like one. You’ll have to build your own controllers to implement the same sort of behavior.

        That’s sort of the point of this blog post; a controller isn’t going to have its -viewWillAppear: method called at the “right” time just because it happens to have a method with that name. Even if the controller is a UIViewController, if you use it ways it wasn’t designed to support (like nesting UIViewControllers’ views) then you won’t get the expected behavior.

      • Jason Elder says:

        And of course the answers are in some of the other comment threads on this post. Which I read several times today. Gaah!
        Sorry for being so dim. And thank you both for your help!

        Replying here as wordpress seems to have run out of reply-to indents.

        • Quang says:

          Do you have any sample code to this? I have been looking at Apple’s PageControl example also and I’m trying to push it on to my UINavigationController stack similiar to what John is doing. The example as stated by John that the PhoneContentController inherits the ContentController which is an NSObject so I cannot add it directly to the navigation stack.

          How can I possibly get around this? I’ve got a structure to something similiar that you have written above.

          -UIViewController
          —UITableViewController
          —–PageControl Here

          I’ve tried removing the NSObject altogether and change the code to just use the UIViewController but I just can’t seem to hit any of the events in debug mode.

  9. If we keep doing this bad stuff, maybe it will eventually be supported. I for one am not rewriting “working” code.

  10. Ilya Genkin says:

    Thanks a lot for explaining that, Jonah.

    Unfortunately for now I cannot see any way other than abusing UIViewControllers for creating navigation framework like in “tweetie” application when you have a Custom Tab Bar controller inside a Navigation Controller. By default you cannot put a UITabBarController inside a UINavigationController as it’s not supported. You have to do the other way around – incorporate UINavigationController inside UITabBarController. But what if you need UITabBarController only on a couple of pages instead of entire application? By creating a custom UITabBarController and using [viewController.view addSubview:someOtherViewController.view] it’s possible to solve the problem.

    Full explanation here: iPhone Tweetie Style Navigation Framework and here: Tab bar controller inside a navigation controller, or sharing a navigation root view.

    Truly speaking I’m eager to see what is the supported way for creating such navigation framework. Do I need to create UITabBarController and then incorporate UINavigationController and then disable UITabBar on all pages except on two non-main paged down in hierarchy? But that’s ridiculous.

    • Jonah says:

      As you say, there’s currently no supported way to use a UITabBarController within a navigation controller. Either you deliberately do so anyway and are prepared to manage the possible bugs or you have to do extra work to build a supported solution.

      Hiding a UITabBarController’s tab bar on most of a navigation controller’s stack is one option.

      You could also create a custom UIViewController which manages several subviews and has it’s own UITabBar, effectively reinventing UITabBarController to suit your needs.

  11. Jonah says:

    Slava Bushtruk has an interesting example of creating a custom container view controller to recreate UISplitViewController style behavior. His implementation forwards view controller lifecycle messages to a pair of sub-view controllers. That means properties like parentViewController and splitViewController won’t be set as expected on the sub-view controllers. I also have to wonder how well it can handle rotation (particularly when the device is rotated while the split view controller is not visible, ie behind a modal). Regardless it looks like a good example of trying to get a working container view controller while still being able to use UIViewControllers to manage the sub-views.

    https://github.com/slatvick/APSplitViewController

  12. Neil says:

    Great post! Got here from StackOverflow where a lot of other iOS programmers seem to have the same problem! I think part of the reason so many people get in a pickle about this is because the multi-view example in a certain very popular beginners book on iphone development loads subviews from NIBs using UIViewController derived classes and adds them to a root view controller’s view. Until now, I never understood why that example was manually sending viewWillAppear:/viewWillDisappear: messages during the view switching code, and now I do! (BTW I have the first edition of this book from over 2 years ago, and there have been new editions since so for all i know this may have changed by now!) For many developers like myself, this is the first multi-view example we see when we first come to iOS. Thanks again!

  13. Bmonke says:

    I’m a total noob to the iOS environment, and am just trying to figure out the basic containers to use. I’m porting over a flash app for iPad that has 3 main views: 1) a custom row of buttons, 2) a group of content window screens tied to those buttons, some of which have two subviews within them, 3) a stage of objects that have the same purpose as 1 to open the windows from 2.

    Only one window is ever opened at a time, when there is no window opened, the user has full view of 2. The screen never changes from this setup; I would assume 1, 2, 3 would all be their own uiviewcontroller. The app will only have to rotate from either side along the longer iPad length. I was going to instantiate all of these through nib files by hooking them up to uiviewcontrollers. But this article is telling me this would create bugs and none of the subviews would receive orientation messages?

    So I was going to have one root uiviewcontroller with these three uiviewcontrollers and each of the windows having it’s own uiviewcontroller. Now it looks like I can only have one uiviewcontroller, with 3 nsobjects as ‘controllers’ with the window nsobject ‘controller’ in charge of each window as a uiimageview. I guess there is a way to nest uiimageviews for any subviews to those and then have the nsobject ‘controller’ parent handle these. Mostly it’s just toggling the hidden property from a uibutton click. Is this correct?

  14. Watch WWDC 2011 Session Video 102: Implementing UIViewController Containment.

    Nesting UIViewControllers will be supported in iOS 5.

    Also, have a look at my app Flickr Gallery (on the iPad). I nest UIViewControllers on here – the Settings and Info panels that pop up are UIViewControllers. I manually call the view lifecycle methods from their containing UIViewController. All works fine, no stability issues. I’m happy with it.

    Thanks
    Dave Casserly

  15. Jason Leach says:

    Hi: Thanks for the post. I find information on generic controllers pretty sparse to say the least.

    When implementing a generic controller do we need to manually implement other mechanics (besides viewDidLoad, …) like gesture recognition, etc? I’m curious if all widgets should work (like UIScrollView) in the UIView managed by a generic controller.

    Regards,
    Jason.

  16. Todd Huss says:

    I occasionally nest UIViewController’s and as long as you understand the pitfalls it works fine! That said, it’ll be great to have it officially supported in iOS 5.

  17. Pingback: Adoption Curve » Blog Archive » links for 2011-08-05

  18. Denis Leal says:

    Great post!!! This is helping me a lot with a project I am working right now. However, I am having a problem with orientation.

    So I have a NavigationController whose RootViewController class has a UITabBar (no UITabBarController) so when I first load the RootViewController I created each view controller for each item from my UITabBar. Trying to show an example through the snippet below:

    -(void) loadView{
    [super loadView];

    // Do any additional setup after loading the view from its nib.
    UIViewController *homeViewController = [[UIViewController alloc] initWithNibName:@”HomeViewController” bundle:nil];

    UIViewController *viewController1 = [[UIViewController alloc] initWithNibName:@”ViewController1″ bundle:nil];

    UIViewController *viewController2 = [[UIViewController alloc] initWithNibName:@”ViewController2″ bundle:nil];

    NSArray *array = [[NSArray alloc] initWithObjects:homeViewController, newsViewController, sportsViewController, nil];
    self.viewControllers = array;

    [self.view addSubview:homeViewController.view];
    self.selectedViewController = homeViewController;

    [array release];
    [homeViewController release];
    [viewController1 release];
    [viewController2 release];

    tabBar.selectedItem = homeTabBarItem;
    }

    And then I show the view from the controller when I select one of the UITabBar items. Again, the UITabBar delegate method is below:

    – (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
    {
    if (item == homeTabBarItem) {
    UIViewController *homeViewController = [viewControllers objectAtIndex:0];
    [self.selectedViewController.view removeFromSuperview];
    [self.view addSubview:homeViewController.view];
    self.selectedViewController = homeViewController;
    } else if (item == tabBarItem1) {
    UIViewController *viewController1 = [viewControllers objectAtIndex:1];
    [self.selectedViewController.view removeFromSuperview];
    [self.view addSubview:viewController1.view];
    self.selectedViewController = viewController1;
    } else if (item == tabBarItem2) {
    UIViewController *viewController2 = [viewControllers objectAtIndex:2];
    [self.selectedViewController.view removeFromSuperview];
    [self.view addSubview:viewController2.view];
    self.selectedViewController = viewController2;
    }
    }

    This solution works perfectly, but I don’t know how to handle Rotation correctly. If I run the app on portrait, it obviously shows the first view on portrait mode. If I rotate the phone to landscape it rotates the view correctly. However, if when in Landscape I choose other option on my UITabBar, the new view controller opens in portrait.

    I understand it is happening because when I load the app and it creates all the controllers I save to the controllers array, they are created in portrait, so when I add it to subview it is gonna show the way it was created. So now I am stuck here, I don’t know how to make the other viewControllers in the array change orientation when I rotate my phone. As a try, I added this code below to the RootViewController but it also didn’t work:

    – (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {

    for (UIViewController *vc in viewControllers) {
    [vc willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
    }
    }

    Anyone has any ideas to help me here? I really appreciate it!

  19. Fred Iannon says:

    Thanks so much for this blog…I can’t believe how confusing \ limiting some of the ios UI stuff is (granted I am coming from the Windows world :-)

    Does anyone know if ios 5 actually includes better support “nested” UIViewControllers ? I searched but couldn’t find much information about the methods discussed in the comments sections that *might* be in ios 5.

    Thanks again,
    F

  20. Vadym Pilkevych says:

    Jonah, thank you for the extremely interesting article!
    I agree with you and it’s hard to argue especially if Apple states in the docs we have to use NSObject-inherited controllers. It’s easy to follow when you’re working on the iPhone app. But it always tempts you to use UIViewControllers in that inappropriate way when you deal with iPad. I think you know what I’m taking about.
    So, what am I suppose to do when I have to place a UITableView which covers only a part of the screen? Make the main view controller inherit from UITableViewController? Or create NSObject-inherited controller which has to take care of the UITableView?

    • Jonah Williams says:

      I would just create a controller inheriting from NSObject to act as the table view’s datasource (and delegate if appropriate). A UITableView filling only a portion of the screen is still going to be a subview of some UIViewController’s view so you can leave it up to the parent controller to handle UIViewController lifecycle messages.

      That’s assuming you’re in a situation where you don’t get to use one of Apple’s provided container view controllers. If that were the case you would be able to safely provide a UIViewController class whose view would be presented as a popover or otherwise partially fill the window.

      • Vadym Pilkevych says:

        Thank you for the quick reply and for the answer itself. Now I know the right way, but in my current project I’ve got the following view hierarchy:

        UIViewController.view (1)
        — …some views (2)
        — UITableViewController.tableView (3)
        — UIViewController.view (4)

        And everything works in expected way. I mean the rotation works good, all the view lifecycle events seem to work correctly. So I did it wrong, but everything works fine. I do not call any viewDidLoad etc methods manually. What am I doing wrong? :) Or the problems appear occasionally?

        • Jonah Williams says:

          Right nesting view controllers won’t necessarily fail immediately. If it did we wouldn’t see so much confusion or developers doing this and then becoming confused when they finally hit unexpected behavior.

          The two things I would worry about in your case are:
          1. Any view controller which uses the `parentViewController`, `navigationController`, or `tabBarController` properties to try to traverse up the view controller hierarchy will get undefined behavior when it hits your nested controllers.
          2. If a rotation event occurs while your view is unloaded I think ‘interfaceOrientation’ is set using a private setter so you can’t propagate that down to your child controllers.

  21. rednic says:

    Thanks for this post I too have learnt a lot … too bad its 4:45 on friday afternoon. You’ve just wiped out my plan for the weekend. Oh well back to the drawing board

  22. Carlos Robinson says:

    Some example? I’m new on iOS and one of my client its really sad because the app its not working fine on iOS 5. I have a view controller with my custom pdfvewcontroler and a scrollview (for thumbnails and navigation between pages.) for all that i used the addsubview method :( and now going from portrait to landscape all the positions are not working.

    • Jonah Williams says:

      What sort of example are you looking for Carlos? If you have been nesting UIViewControllers I would not be shocked to hear that your nested controllers do not receive the same orientation change updates unders iOS 5 as they did in iOS 4.

      Returning `YES` from the `-automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers` method introduced in iOS 5 might change the behavior back to what you saw in iOS 4 but I wouldn’t count on it since you are probably not using `-addChildViewController:`.
      Instead you would probably need to forward the `-willRotateToInterfaceOrientation:duration:`, `-
      willAnimateRotationToInterfaceOrientation:duration:`, and `-
      didRotateFromInterfaceOrientation:` methods to your child controllers yourself but be aware that doing so may result in those messages being sent to the child controllers twice on iOS 4 devices.
      To get more reliable behavior on both iOS 4 and 5 I would use runtime checks to use the container view controller support in iOS 5 when it is available and fall back to your existing implementation otherwise.

  23. Pingback: Mind Mash » Not abusing UIViewControllers

  24. Anonymous says:

    Thanks very much for this article. Really hit the spot in terms of my understanding of how View controllers work (or rather, don’t work).

    I think I’ll just put some lifecycle stuff in for now though!

  25. Magingax says:

    Thank you for clear advice..But I still have fundamental question..WHY SHOULD I USE UIVC ?? I wrote application completly without UIVC..Just defalult UIWindow and several subclassed UIView..It may be silly question..but no one gave me answer..Just said. It’s MVC pattern..blah,blah..

    • Jonah Williams says:

      Writing an application without any UIViewControllers seems fine to me. As long as you have no need or use for the behaviors a UIVC provides. View controllers give you a convenient mechanism for managing one “screen” and switching between them. If you you have no need for that behavior then don’t use it but UIVCs capture a set of behaviors most iOS apps do want to use.

      UIVCs alone won’t automatically make you follow a MVC architecture and you can certainly implement a MVC pattern without UIVC if you want to. I would however suggest that you are very likely to benefit from introducing the separation of concerns between your models, views, and controllers to reduce the complexity of each class, make changes easier in the future, make class reuse easier, and to produce code which is easier for everyone (including yourself) to understand. I also think that in most cases UIVCs will help lead you in that direction and provide behavior your app is likely to need.

  26. Nick Moores says:

    Just stumbled upon this. I’ve only recently started looking at iOS development, but have been really puzzled about how to organise viewcontrollers and the view hierarchies, as they seem to get out of hand very quickly. I’m not sure whether it’s because I’m used to doing things differently (the .NET way) but I was quite surprised by the limitations that you’ve described, and agree with a lot of what you’ve said.

    You offer what sound like reasonable alternatives, but deep down, I really hope that something within the iOS changes to allow for a better way of doing all this.

    • Jonah Williams says:

      With iOS 5 providing an API for nesting UIViewControllers when necessary I don’t think further changes are necessary. I don’t have enough experience with .NET to make that comparison but from what I can see the most common problem which leads iOS developers to this post is assuming that all controllers must or should be UIViewControllers.
      I think that once you recognize UIViewController as a very specific type of controller and not your only option then you have a good set of tools for building reasonable view and controller hierarchies.

  27. This is a fantastic, fantastic article. It makes so much sense, I cannot believe I hadn’t read about it before. Thank you.

  28. Pingback: Abusing UIViewControllers | The Kitchen Drawer

  29. David Roth says:

    Thanks for the post – looking forward to seeing how this works out in iOS 5.

  30. Krzysztof Zablocki says:

    Hey, try my solution:
    http://www.merowing.info/2012/02/uiviewcontroller-containment/
    I used this in a few apps avaiable in appstore and I never had any problems :)

  31. Hi I’m newer in iOS programming i’ve intsaled Xcode 4.3 with iOS SDK 5.1, and i can’t find the medthod in the veiwcontroler.m -(void) viewWillAppear… like i’ve sad im a newer , so plz give me a hand because im following a course wich he use Xcode 4.2 ..

  32. Pingback: Reusing UITableView Header Views+Controllers | Isaac Schmidt

  33. Pingback: Quora

  34. Pingback: Solo tenemos un controller. (¿Raro no?)

  35. Pingback: Using a View Controller managing two other View Controllers | PHP Developer Resource

  36. Mark says:

    If you call [super viewDidAppear:animated] etc inside the view controller that spans the whole screen, it will actually pass the message down to all the view controllers whose views are subviews.

  37. James Wang says:

    I guess you made a mistake. In PageControl sample, the PhoneContentController is a generic control which control the entire screen. MyViewController is a UIViewController that its view is added to the scrollview.

    • JonahWilliams says:

      No mistake. Unfortunately Apple’s sample code often contains shortcuts or full anti-patterns in order to quickly demonstrate one particular topic. In the case of PageControl the goal is to demonstrate UIScrollView paging. While it does do that I would not use the project as an example of a reasonable way to load content views for those pages.

      I’m not just arguing over code style here. PageControl’s approach breaks UIViewController behavior you might be relying on. For example: -viewWillAppear:animated: is called once for each page’s view controller no matter how many times it appears on screen, -viewWillDisappear is never called, and the view controllers presenting those pages cannot reliably present modal view controllers. View unloading, rotation, and other events will probably reveal more surprises.
      Using this sort of approach you must either send a number of UIViewController lifecycle messages yourself (while trying not to duplicate the few those view controllers actually do receive) or work with a UIViewController that only does some of the things all other UIViewControllers can do. Either way is seems likely to surprise you or anyone else touching that code in the future.

  38. This is just a great article, wondering if anyone sort out a better method for a multiple view (non tabbar, non controllerView app)

  39. not convinced says:

    sorry, i’m not 100% convinced that you are right. why would Apple allow developers to implement child viewControllers, if it wasn’t recommended ?

    the sub view controllers do not need to know about orientation, it only affects the top most controller (and it’s view), as a transformation matrix on it’s layer, actually. any other controller doesn’t have to care about orientation, just about the dimensions of it’s view.

    • JonahWilliams says:

      Child view controllers have been supported since iOS 5 but this post predates that release by several months.

  40. Pingback: Am I abusing UIViewController Subclassing? | Ask Programming & Technology

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>