Using Custom View Controllers to manage different portions of the same view hierarchy - iphone

The View controller programming guide states this regarding view controller's usage:
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.
I understand that if we use multiple custom view controller's to control the parts of a view (i.e. a view controller to manage subViews of a main view which in turn is managed by a view controller) the default methods like:
didReceiveMemoryWarnings
viewWillAppear
viewWillDisappear
viewDidUnload
etc. etc. will not be called.
Apart from this, is there any other solid reason why we should not be using multiple view controllers to manage the respective subviews of a view?
The documentation also provide an alternative solution which reads as:
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.
But there is no mention as to why multiple view controllers should not be preferred. My question is:
Why should not we prefer it this way?
I am concerned because I prefer using UIViewController's subclass to manage my views since I load them from nib each time and I segregate nibs for each view controllers. It becomes easy to cater the changes in later stages of the project. Is this wrong? Should I necessarily change my programming style, or is it ok if I go ahead with this approach?
Thanks,
Raj

Well, I'd say "as long as it works", you can keep on doing like you do !
But to keep things "cleaner", I'd use my own objects.
Since ViewControllers are designed with other general features in mind (like working with navigation controllers and tab bar controllers), which makes it a bit "heavy" for a simple usage, like you do.
Plus, like you mentioned, some events are only called when the viewController's view is added to the main window.
Can't you use your own objects with Interface Builder ? If you create one (or several) UIView IBOutlet(s), it should work the same.

I have an app that does use two UIViewControllers on a single screen. The child is a UITableViewController. I don't rely on any of the UIViewController behavior of the child -- only the UITableViewController methods. This is convenient because there are other cases where the child UITableViewController does manage the whole screen. And in that case, it does use the UIViewController methods. Questionable design? Maybe. It has worked fine for two years now. But I'm not sure I would recommend the pattern.

Related

Why use a ViewController to manage ViewControllers and not just the ApplicationDelegate

If I have a bunch of ViewControllers that are only ever going to deal with a single view, or even if my ViewController is going to deal with multiple views, why should I use another ViewControllers to manage the other ViewControllers? Why wouldn't I just change out ViewControllers at the ApplicationDelegate level?
Maybe I'm thinking about ViewController the wrong way? I'm used to writing in the MVC pattern with Ruby/.NET. For an example, if I were working with widgets I'd probably have a WidgetController and a List view, and a Detail view for the WidgetController.
What is the analogous iPhone MVC construction? I'd assume the WidgetController would subclass the ViewController and I'd have a couple different views depending on how I wanted to look at the widget data. Then, when I wanted to deal with Wodgits I'd make a WodgitController with its associated views and swap the window's subview out with the new Wodgit ViewController.
I don't see what having a RootViewController to control my controllers buys me. Where is the value? What am I missing?
I'll focus on one aspect: Compartmentalization is a very useful design principal. If you separate concerns and put things where they "belong", they will be easier to find, maintain, build upon, and co-opt for new purposes.
Consider this:
All the pipes in your house go to the same place. Do you urinate in your sink, or do you use the fixture designed for that purpose?
In the case of your app, the application delegate is responsible for responding to application events and managing universal application resources. The root view controller is responsible for managing your views and view controllers. As a view controller it has special abilities to do so with navigation control, the ability to present modal views, act as a delegate, etc. While you could create a view controller and manipulate it completely in the application delegate, the task will become exponentially more difficult as you multiply the number of views you are managing, especially if you wish to be a text field delegate, data source, etc. On the other hand, it is easy to split the root view controller into its own class. There is very little cost.
Why pee in the sink when the toilet is a step away?

iAd and view controllers

Apple's documentation on the UIViewController class has this to say:
Note: You should not use view controllers to manage views that fill only a part of their window—that is, only part of the area defined by the application content rectangle. If you want to have an interface composed of several smaller views, embed them all in a single root view and manage that view with your view controller.
When using iAd, this is a pain. The problem is that I don't want to be managing my app's content in the same class that has methods like moveBannerViewOffScreen, didFailToReceiveAdWithError, etc. Putting those in the same class reduces modularity. Additionally, I can't have a navigation stack in which the ad remains in place when other view controllers are pushed.
I am curious if readers have encountered similar issues and if so, how you have dealt with them?
If you want modularity while keep using ivars from the view controller, create a category.
The two approaches that come directly to mind are using a non-UIViewController class as your delegate, or using a subclass of UIViewController as your view controller base class and putting the logic in there.

Multiple view controllers on screen at once?

I am trying to wrap my head around controllers in Cocoa Touch. The main problem is that I would like to have more than one controller “on screen” at once – I want to have a large view (with controller A) composed of smaller views controlled by their own controllers (say B). I’d like to have it this way because the division makes the code much cleaner. What’s bad is that the additional controllers (of type B) are not “first-class citizens” on the screen, for example they do not receive the autorotation queries and notifications. (And cannot easily display modal controllers, they have to send the presentModal… message to their parent controller.)
What is the difference between the A and B controllers from Cocoa viewpoint? Does the system keep some kind of pointer to the “frontmost controller”, a privileged one to which it sends notifications and such stuff? Why don’t the other controllers receive them, even though their views are on the screen? Is having multiple controllers “on screen” considered a hack? Or is it supported and I am just missing some point? Thank you.
More about the problem I am trying to solve: I am writing a simple photo browser. Photos are displayed in full screen, user can swipe left or right to change photos. The A controller takes care of the scrolling part and the B controllers take care of each photo itself.
Isolating B seemed like a good idea, since the photos are loaded from network and there is a lot that can happen, like the network might be down et cetera. In the B controller the code is fairly simple, since B only works with one particular photo. If I moved the code to the A controller, things would get messy.
The only thing I don’t like about the current solution is that I have to manually work around B not being a “first-class” controller. I have to pass some calls manually through A to B and when B wants to display a modal dialog, it has to send the presentModal… to A. Which is ugly.
There is now a first-class support for this scenario since iOS 5, it’s called controller containment.
swift controller containment
objc controller containment.
It's not closely related to the original question but important. Apple clearly states in View Controller Programming Guide that a view controller is responsible for controlling exactly one screen's content:
"Each custom view controller object you create is responsible for managing exactly one screen’s worth of content. The one-to-one correspondence between a view controller and a screen is a very important consideration in the design of your application. You should not use multiple custom view controllers to manage different portions of the same screen. Similarly, you should not use a single custom view controller object to manage multiple screens worth of content.
Note: If you want to divide a single screen into multiple areas and manage each one separately, use generic controller objects (custom objects descending from NSObject) instead of view controller objects to manage each subsection of the screen. Then use a single view controller object to manage the generic controller objects. The view controller coordinates the overall screen interactions but forwards messages as needed to the generic controller objects it manages."
However in iPad Programming Guide they also say that there may be container view controllers:
"A view controller is responsible for a single view. Most of the time, a view controller’s view is expected to fill the entire span of the application window. In some cases, though, a view controller may be embedded inside another view controller (known as a container view controller) and presented along with other content. Navigation and tab bar controllers are examples of container view controllers."
Up to my current knowledge I would not use sub-view controllers in a view controller but try to subclass NSObject and send messages to them from my main view controller.
Also check this thread:
MGSplitViewController discussion
First, and this is important, view controllers don't get "on screen" -- views do. Your "top level" controller can certainly pass along the kinds of messages you're describing to its "sub-view-controllers". In fact, this is how most apps work. Consider an app that has a tab bar, and where the views use navigation controllers. You actually have several view controllers "running" at the same time, each with its own view on screen at once -- your "root" view controller will be an instance (or subclass) of UITabBarController, which then has several nested UINavigationControllers, each which will display nested view controllers (like an instance or a subclass of UITableViewController).
You might want to read up a bit on how responder chains work. Consider a touch event. It will be generated for the view closest to the top of the stack, that can receive events, which is also underneath the tap. If that view can't handle it, it gets passed up the view hierarchy food chain until something deals with it (and then eats it).
As to the specifics of your question, on the whole, I'm not sure exactly what the strategy you describe is really doing to benefit you in terms of complexity. It depends on how exactly you're implementing things, but having separate view controllers for each little subview may require more bookkeeping code than just having one view controller that knows about all its sub-view components.
This is a pretty old question, but since I guess there are people who might face the same problem today I'd like to share my solution.
I was writing this application that had this one screen with a lot of information, pagination, controls etc. Since according to Apple's MVC documentation on the role of ViewControllers, you should not implement the logic in view itself, or access the data model directly from it, I had to choose between having a Massive ViewController with a few thousand lines of code which was both hard to maintain and debug(even with unit tests) or find a new way.
My solution was to use UIContainerView like below:
this way, you can implement each part's logic in it's own ViewController, and the parent view controller takes care of constraints and sizing of the views.
Note: This answer is just a guide to show the way, you can find a good and detailed explanation on how it works and how to implement it HERE
Actually you can make it work earlier than iOS 5, since most of us are targeting 4.x and 5.x at the same time. I've created a solution that works in both, and it works great, few apps in appstore use it :) Read my article about this or just download and use a simple class that I've created for this purpose.

Why shouldn't a UITableViewController manage part of a window in Cocoa Touch?

I have a view that contains a UITableView and a UILabel which works perfectly as far as I can tell. I really don't want to manage the UIView and UITableView with the same controller as the UITableViewController handles a lot of housekeeping and according to the documentation:
If the view to be managed is a
composite view in which a table view
is one of multiple subviews, you must
use a custom subclass of
UIViewController to manage the table
view (and other views). Do not use a
UITableViewController object because
this controller class sizes the table
view to fill the screen between the
navigation bar and the tab bar (if
either are present).
Why does Apple warn against using it and what will happen if I ignore this warning?
Update: Originally I quoted the following from the Apple Documentation:
You should not use view
controllers to manage views that fill
only a part of their window—that is,
only part of the area defined by the
application content rectangle. If you
want to have an interface composed of
several smaller views, embed them all
in a single root view and manage that
view with your view controller.
While this issue is probably related to why UITableViewController was designed to be fullscreen, it isn't exactly the same issue.
The major practical reason to use only one view controller per screen is because that is the only way to manage navigation.
For example, suppose you have screen that has two separate view controllers and you load it with the navigation controller. Which of the two view controllers do you push and how do you load and reference the second one? (Not to mention the overhead of coordinating the two separate controllers simultaneously.)
I don't think using a single custom controller is a big of a hassle as you think.
Remember, there is no need for the TableviewDataSource and the TableViewDelegate to be in the actual controller. The Apple templates just do that for convenience. You can put the methods implementing both protocol in one class or separate them each into there own class. Then you simply link them up with the table in your custom controller. That way, all the custom controller has to do is manage the frame of tableview itself. All the configuration and data management will be in separate and self-contained objects. The custom control can easily message them if you need data from the other UI elements.
This kind of flexibility, customization and encapsulation is why the delegate design pattern is used in the first place. You can customize the heck out of anything without having to create one monster class that does everything. Instead, you just pop in a delegate module and go.
Edit01: Response to comment
If I understand your layout correctly, your problem is that the UITableViewController is hardwired to set the table to fill the available view. Most of the time the tableview is the top view itself and that works. The main function of the UITableViewController is to position the table so if you're using a non-standard layout, you don't need it. You can just use a generic view controller and let the nib set the table's frame (or do it programmatically). Like I said, its easy to think that the delegate and datasource methods have to be in the controller but they don't. You should just get rid of the tableViewController all together because it serves no purpose in your particular design.
To me, the important detail in Apple's documentation is that they advise you not to use "view controllers [i.e., instances of UIViewController or its subclasses] to manage views that fill only a part of their window". There is nothing wrong with using several (custom) controllers for non-fullscreen views, they just should not be UIViewController objects.
UIViewController expects that its view takes up the entire screen and if it doesn't, you might get strange results. The view controller resizes the view to fit the window (minus navigation bars and toolbars) when it appears, it manages device orientation (which is hard to apply correctly if its view does not take up the entire screen) etc. So given how UIViewController works, I think there is merit to Apple's advice.
However, that doesn't mean that you can't write your own controller classes to manage your subviews. Besides the things I mentioned above, interacting with tab bar and navigation controllers, and receiving memory warnings, there isn't really much that UIViewController does. You could just write your custom controller class (subclassed from NSObject), instantiate it in your "normal" fullscreen view controller and let it handle the interaction with your view.
The only problem I see is the responder chain. A view controller is part of the responder chain so that touch events that your views don't handle get forwarded to the view controller. As I see it, there is no easy way to place your custom controller in the responder chain. I don't know if this is relevant for you. If you can manage interaction with your view with the target-action mechanism, it wouldn't matter.
I have an application where I did use 2 separate UIViewController subclasses below another view controller to manage a table view and a toolbar. It 'kind of' works, but I got myself into a massive pickle as a result and now realize that I should not be using UIViewController subclasses for the sub controllers because they contain behavior that I don't need and that gets in the way.
The sort of things that went wrong, tended to be:
Strange resizing of the views when coming back from sub navigation and geometry calculations being different between viewWillLoad and viewDidLoad etc.
Difficulty in handling low memory warnings when I freed the subview controllers when I shouldn't have done.
Its the expectation that UIViewController subclasses won't be used like this, and the way they handle events, using the navigation controller etc that made trying to use more than one UIViewController subclass for the same page tricky because you end up spending more time circumventing their behaviour in this context.
In my opinion, The Apple Way is to provide you the "one" solution. This served the end-users very well. No choice, no headache.
We are programmers and we want to and need to customize. However, in some cases, Apple still doesn't want us to do too many changes. For example, the height of tab bar, tool bar and nav bar, some default sizes of the UI components(table view), some default behaviors, etc.. And when designing a framework and a suite of APIs, they need to nail down some decisions. Even if it's a very good and flexible design, there is always one programmer in the world wants to do something different and find it difficult to achieve against the design.
In short, you want a table view and a label on the same screen, but they don't think so. :)

Multiple views and controllers in UITabBarController

I'm trying to add multiple views inside a UITabBarController. Currently my object hierarchy looks like this: UITabBarController -> UIViewController* -> UIView*. As a more concrete example, the first view controller for my UITabBarController is a UIViewController, and that has three subviews, which are controlled by a UISegmentedControl. Depending on what segment is selected, I push the corresponding view to the front.
I understand that I can use a UINavigationController to manage my three views; however, the data I wish to present is not really hierarchical.
Are there examples of container controllers other than UITabBarController or UINavigationController that I can use for this case? Or is there another approach I should use (I'm currently managing views manually).
Thanks!
Custom view controllers are covered in the View Controller Programming Guide.
If you wanted to change your layout to use the UINavigationController you could remove the segmented control view and have the first view be a table view inside a nav controller. The table would have the three options the segmented control had and tapping on them would push the view associated with that option. This way you've created a hierarchical view layout rather than using the segmented control, which is typically used to toggle functionality rather than control views.
If you choose to do this, these two guide sections would be a good place to start.
There's not really any supporting framework for this - usually you have to manage switching out views in a switched container view yourself.
One approach I have taken in the past is to maintain an array of ViewControllers for each switching view, and take the viewController.view to add as a subview of your switched container view. Then I write code around the switching of the view controllers to call viewWillAppear and viewWillDisappear on the contained view controllers as they are swapped in and out, that makes things much simpler since you can treat them totally separately.
You can write that class kind of generically and then re-use it.