averydev averydev - 1 year ago 65
iOS Question

Am I abusing UIViewController Subclassing?

In trying to figure out why viewWillAppear wasn't being called in my app I came across what may be a gross misunderstanding I hold about the intended use of UIViewController subclasses.

According to the following post viewWillAppear does not run when using addSubView!!! and the link to this blog post: http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/ UIViewController subclassing should only happen in very specific situations. Most notably when added directly to the UIWindow, or other Apple created custom controllers like UINavigationControllers.

I'm certainly guilty of adding the views of UIViewController subclasses to the views of other UIViewController subclasses.

In fact, I thought this was more or less the whole idea of Apple's implementation of MVC generally... One overall VC, with other VCs beneath, all happily getting their delegate methods called.

If there are a lot of views (which by definition need controlling) coming and going in an app, and lots of screenfulls, in the model described in that post, each screenfull should have one master VC Subclass, with all the subviews controlled instead by custom controllers (which happen to control views) which are subclasses of simple NSObject.

In this case, UIViewControllers should only be directly to the Window or UINavigationController, UITabBarController etc?

Are you guaranteed to get the UIVC Delegate methods called in that case? How Does this differ from calling the delegate methods manually when a viewcontroller's view is a subview of another VC?

Honestly this seems like a tremendous waste of time. Custom implementations of ViewDidLoad, viewDidLoad, viewDidUnload, viewWillAppear, viewWillDisappear not to mention things as simple as properties like, say, "view"...

So basically, either I have been completely wrong, or I'm on a wild goose chase. If UIViewController subclasses can't be counted on to call viewWillAppear, why not just call that method manually, and be done with it?

Why replicate all of the perceived functionality of UIViewController?

Answer Source

Answer to title question: Yes.

So basically, either I have been completely wrong, or I'm on a wild goose chase.

It sounds like you've been completely wrong. The term "view" has a few different but related meanings:

  • A view is, of course, any object that's an instance of UIView or a subclass of UIView.
  • In the context of MVC, "view" is used collectively, and we talk about this or that being "the view's responsibility" even though "the view" is really a group of objects.
  • When talking about a view controller, the "view" that the controller manages is the UIView instance that the controller's view points to and the hierarchy of subviews that it contains.

It sounds like your misunderstanding is on this last point. A view controller should manage a single "screenful" of content. If you're using a single view controller object to manage more than one view hierarchy, or if you're using several view controllers to manage different parts of the same view hierarchy, you're using UIViewController in a way which was never intended and which is likely to lead to problems.

The methods that you mentioned (-viewDidLoad, -viewWillAppear, etc.) are meant to tell the view controller that its view hierarchy was just loaded, is about to be displayed, and so on. They're really not meant to refer to an individual subview, and it would be unusual for a view controller to need to be given that information for individual subviews. If the view hierarchy was loaded, then the view controller knows that everything in that hierarchy was loaded.

You seem to be interpreting these methods as delegate methods, but they're not. A delegate is a separate object that allows for customization of the delegator without the need for subclassing. -viewDidLoad and -viewWillAppear are two examples of override points for UIViewController, a class that's intended for subclassing. The view controller object calls these methods itself to give subclasses a chance to take some action at an interesting point in the controller's life cycle.

If UIViewController subclasses can't be counted on to call viewWillAppear, why not just call that method manually, and be done with it?

Take a good look at UIViewController and you'll see that most of the functionality provided has to do with displaying the view (that is, the view hierarchy) on the screen, or with integrating the controller with "container" view controllers such as UINavigationController and UITabBarController. None of that is useful to objects that aren't managing the entire screenful of content.

It happens sometimes that a group of views will replicated on several screens, and in some of those cases it's helpful to manage those views with an object that's separate from the view controller itself. I can see how you'd be tempted to use UIViewController because of its -viewDidLoad and similar methods, but those are really only a small part of what UIViewController does. What would it mean to call -presentModalViewController: on one of those objects? Or to access its navigationController or parentViewController properties?

If you really want to manage subviews of your view controller's view hierarchy using those methods, create a subclass of NSObject that has -viewDid[Load|Unload|Appear|Disappear] and -viewWill[Appear|Disappear] methods. You can create that class once and then subclass it as often as you need to, and none of your "subcontroller" classes will have all the extra, unneeded controller management stuff that comes along with UIViewController.

Edit: I want to add a pointer here to Apple's View Controller Programming Guide for iOS, which provides a lot of support for what I've laid out above. Here's a relevant passage from the subsection titled "View Controllers Manage a View Hierarchy":

View controllers are directly associated with a single view object but that object is often just the root view of a much larger view hierarchy that is also managed by the view controller. The view controller acts as the central coordinating agent for the view hierarchy, handling exchanges between its views and any relevant controller or data objects. A single view controller typically manages the views associated with a single screen’s worth of content, although in iPad applications this may not always be the case.

View Controller Programming Guide is required reading for anyone even thinking of writing an iOS app. It's worth reviewing if you haven't read it in a while (or ever).

Update: Starting with iOS 5, it's now possible to define your own container view controllers, i.e. view controllers that manage other view controllers and potentially display the views of multiple view controllers at the same time. You can read more about it in the guide linked above in the section entitled Creating Custom Container View Controllers. None of this really changes the essential points above: a single view controller should still manage a hierarchy of views, and methods like -viewDidLoad still refer to the entire view graph rather than to individual subviews. The advice that a view controller manages an "entire screenful" of content is no longer completely accurate -- just as UISplitViewController has displayed content from two view controllers simultaneously ever since the introduction of the iPad, your own containers can now show the views of multiple child view controllers. Writing a container view controller is a somewhat advanced topic -- you should be very familiar with the use of view controllers generally and the way the provided container view controllers work before you take a stab at creating your own.