I've seen custom subviews implemented as an UIViewController subclass, but that maybe could have been implemented as an UIView subclass.
When should I subclass UIViewController instead of UIView for a subview? Are there any drawbacks to subclassing UIViewController?
Personally, when I need some significant logic to go on, I do it with a UIViewController subclass. Also, if I am looking for some of the behavior that you get from UIViewController e.g. presenting it modally or in a navigation controller.
If you are doing something fairly simple or lightweight, a UIView subclass is usually enough. I seem to have used them most often when making custom buttons and table view cells.
In my experience I have found myself using more UIViewController subclasses than UIView subclasses, but this might not be the best, it just so happens that I feel a bit more comfortable using view controllers rather than straight-up views.
Take a look at what Apple has to say on Controller Objects and the MVC design pattern
In iOS controller are generally expected to fill at least one the following roles:
Coordinating controllers provide application specific logic. They respond to delegate messages, notifications, and IBActions. Coordinating controllers also setup connections between other objects and often manage the creation and destruction of those objects.
View controllers, specifically UIViewControllers, manage the display of one "screen" worth of content and trigger transitions to the next "screen". They respond to memory warnings and rotation events.
Mediating controllers exist in OS X but their role is usually filled by view controllers in iOS. They act as an intermediary between views and models; updating models when views receive input and updating views when models change.
If the behavior you are implementing fits into one of these categories you probably want to create a controller object. If your logic is only concerned with the display of data (and possibly responding to user input) then perhaps it belongs in the view layer. If your logic is about the data itself then it probably belongs in the model.
If you can't find a good fit for your logic in any of those layers then you probably should model it differently as a combination of different responsibilities which belong on different objects in different layers. ie a view which requests data to display from a mediating view controller.
You would also subclass the UIViewController if you're going to use an AdBannerView in your "view". AdBannerView needs a UIViewController to be able to work.
The thumb rule I follow is, If you are doing custom drawing, subclass UIView. Otherwise, subclass the UIViewController.
Related
After watching the Stanford iTunesU CS193p online course I have quick question regarding the correct way to implement an MVC design.
Usually when I do an application I set my view up as part of the controller and add UIButtons, UILabels etc. to that. Essentially the controller and the view become one, unless you count the UIObjects as being the view objects
In one of the early CS193p examples the tutor splits the view off from the controller as a separate object (subclassing UIView), before implementing a protocol and delegate property on the view which the controller object then conforms to.
I am just curious about the practicality of the CS193p design, I can understand that splitting off the view better represents the MVC design paradigm (especially as a teaching aid) but slightly sceptical of its application in real world applications.
Think about the "view" as objects such as UIButton and UIImageView; re-usable components that don't necessarily know anything about the rest of your application. Your view controller is the object responsible for configuring the view objects and managing their state.
There are times when you'll want to subclass UIView, mostly when you need to do custom drawing in drawRect:. You wouldn't subclass UIView to customize behavior though, that's what your view controller is for. For example, you wouldn't subclass UITableView and just to make the table view its own delegate. Instead you'd make your view controller the table view's delegate.
With iOS, the UIView class implements drawing refresh and touch handlers, not the view controller class. If you need drawRect: or touchesBegan:, etc., you will need a separate UIView subclass.
This partially could be because this is how buttons and labels themselves behave as well. They redraw themselves, and delegate processed touches. The view controller doesn't draw their content and track xy locations.
It's not strictly necessary. If you use Interface Builder, most of the reasons why you would build a custom view (breaking UI setup code out of your business code) are taken care of. However, if you want to build custom actions to your view, or setup some properties that you can't quite get at with IB, then it would make sense to split off your view into a separate class.
In the app represented by the image below, I'm currently using three UIViewControllers: One master view controller, one for the main menu, and one for a settings screen which is launched by the main menu. As I'm learning more about how UIViewController works and what it's designed for, I'm questioning the wisdom of my architecture.
It seems to me that the main point of subclassing is to be able to override the methods which get called automatically during the life cycle of the controller: viewDidAppear, viewWillAppear, willRotateToInterfaceOrientation, etc. It appears that these methods are only called if the UIViewController (or subclass) is part of the UIViewController hierarchy. Therefore, there's no point in subclassing UIViewController unless I'm going to use one of the standard means of creating a viewcontroller hierarchy i.e. UINavigationController, [UIViewController presentModalViewController] etc.
I'm wary of using the Cocoa-style means of adding view controllers to the hierarchy because they all seem to be very restrictive. For example, I could display my settings screen using [UIViewController presentModalViewController], but but I don't want it to obscure the entire screen. There's background animation which I want the user to be able to interact with even while the settings screen is visible.
Here are my questions:
1) Is it silly to subclass UIViewController unless I'm going to be adding it to the viewController hierarchy via one of Apple's techniques?
2) Am I correct in my assumption that the built-in means of displaying new views are too restrictive for me, and in order to have the flexibility I want, I'm going to need to just load views via [view addSubview]
3) If it's true that subclassing UIViewController makes no sense for my menu and settings views, how should I avoid having all of my code in one monster UIViewController subclass. Should I just subclass NSObject, add the appropriate IBOutlets and IBActions and pass that in as the File's Owner when I load the nib using [NSBundle loadNibNamed]?
Good question. First, a point of clarity: What you refer to as "one of Apple's techniques" is referred to in the UIViewController Programming Guide as "indirect presentation", and includes things like modal presentation, being pushed on a navigation stack, presenting a popover controller, etc. Basically all of these view controller methods are considered "indirect" presentation methods, while the use of -addSubview: (something like [someView addSubview:myViewController.view]) is considered "direct" presentation.
From said programming guide: (Giant Block Quote...)
It is recommended that you use only
the suggested techniques for
displaying the views of your view
controllers. In order to present and
manage views properly, the system
makes a note of each view (and its
associated view controller) that you
display directly or indirectly. It
uses this information later to report
view controller-related events to your
application. For example, when the
device orientation changes, a window
uses this information to identify the
frontmost view controller and notify
it of the change. If you incorporate a
view controller’s view into your
hierarchy by other means (by adding it
as a subview to some other view
perhaps), the system assumes you want
to manage the view yourself and does
not send messages to the associated
view controller object. (emphasis mine)
Apart from your setting up your
application’s initial interface, most
other views are presented indirectly
through their view controller objects.
All that is to say that you are correct in thinking that all of those UIViewController messages will be wasted if you if simply add the view to a view hierarchy directly, and take no other further action (key window being the exception). That quote also mentions that it is most common to use indirect presentation.
1) I hesitate to make a blanket statement and say "Yes, in all cases, it is silly to subclass UIViewController unless you're presenting it indirectly." I'm sure there is some good use for it somewhere. I'll settle for saying that I have personally never done so.
2) Absolutely, I would not use a UIViewController subclass here.
3) Allow me to direct your attention to another area of The Programming Guide:
In iPhone applications, the views in a
view hierarchy traditionally cover the
entire screen... 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.
That pretty clearly syncs up with what you're wanting to do here. You're dead on with your self suggested approach. That "Settings Screen launched by main menu" should be managed by a generic controller object descending from NSObject, which is in turn managed by your full-screen UIViewController subclass.
I'm currently building a tabbed iPhone application where each tab's view controller is an instance of UINavigationController, and where every subcontroller of every one of the UINavigationController instances is an instance of UITableViewController. Ideally, I'd like to subclass UINavigationController so that the controller for each tab is a subclass of UINavigationController that (in addition to having all the standard UINavigationController functionality, obviously) serves as the datasource and the delegate for each of the table views associated with its subcontrollers. Trying to do this seems to break the basic UINavigationController functionality in the subclass.
Seeing as Apple says in their iPhone documentation that one shouldn't subclass UINavigationController, and things seem to break when one does, I'm wondering how I should go about extending UINavigationController's functionality without subclassing, and generally speaking, how one should work around subclassing limitations when doing Cocoa development.
Thanks!
Why on earth do you want the UINavigationController to act as a datasource for the table? The whole point of UITableViewController is that you subclass it, and it acts as the datasource for the UITableView that it also places in, and fills, the parent view.
For reference, note that since iOS 6, UINavigationController can be subclassed legally.
This class is generally used as-is but may be subclassed in iOS 6 and later.
UINavigationController Class Reference
Of course, that doesn't mean you always should. But you can.
I'm going to go ahead and say your idea has some merit, if at every level you are truly using the same kind of data and each level perhaps has a different delegate to handle cell creation.
Basically there's no reason you cannot subclass UINavigation controller to add a totally orthogonal layer of data atop it, because it has nothing to do with the UI or behavior that UINavigationController is managing (which is what Apple is concerned you will be messing with). To those opposed to the idea, think of it as a per-tab data store that all pages in a tab can access instead of every page in the system having to go to the AppDelegate, or have a bunch of singletons. Well basically it's a singleton, but at least one that is already there and gets the reference passed around automatically.
All that said, I'll end with an alternate design proposal - I think what you are probably wanting to do is drill down through multiple layers reusing the same code to generate cells and such because you have the same kinds of data at each layer. A better approach to handle that is to have one view controller that you feed the subset of data to display, and when a user drills down it simply creates another instance of the same view controller with that new subset of data. That approach is much better than the idea of having the navigation controller act as a table delegate for each level, because you'd have to do a ton of rewiring moving back and forth and it would require even more work to remember scroll position at each level to boot. That's why you want to keep drill-downs using multiple instances of view controllers, but multiple instances doesn't have to mean multiple classes.
As I understand it, subclassing is not encouraged because Objective C allows a subclass too much access to the internal workings of its superclass.
The suggested alternative to writing a subclass is to write a delegate, in this case a UINavigationControllerDelegate. You can then encapsulate the specific behavior you want to extend into this delegate class, and link it up to the UINavigationController whenever you need it.
If the available controller heirarchy does not serve your needs in terms of handling data (my assumption, since we don't know why you want one object to be the datasource for multiple views), you can always create additional data and/or controller classes (subclasses of NSObject at least).
You can have a data or other object persist while changing views in various ways. (1) a property of your application delegate class. Any object in your app can get your app delegate instance with
[[UIApplication sharedApplication] delegate]
Use this sparingly since it's essentially creating globals.
(2) You can pass data or other objects from controller to controller as you push view controller subclasses or bring them up within tabs.
(3) Core Data is another way but requires a lot of Cocoa under your belt, and you still have to manage the context instance(s).
Because they want to avoid the UI inconsistency that plagues every other platform.
I have always sort of wondered when to use a UIView vs. a UIViewController on the iPhone.
I understand that you shouldn't use a UIViewController unless it's a full-screen view, but what other guidelines are there?
For example, I want to build a modal overlay - a screen that will slide into place over the current screen. If this modal overlay is full-screen, should it be a UIViewController? The last time I built something like this, I subclassed UIViewController, but now I wonder if that was correct.
From Apple's View Controller Programming Guide for iOS:
"The most important role of a view controller is to manage a hierarchy of views. Every view controller has a single root view that encloses all of the view controller’s content. To that root view, you add the views you need to display your content."
Also:
"There are two types of view controllers:
Content view controllers manage a discrete piece of your app’s content and are the main type of view controller that you create.
Container view controllers collect information from other view controllers (known as child view controllers) and present it in a way that facilitates navigation or presents the content of those view controllers differently.
Most apps are a mixture of both types of view controllers."
This is a great question.
My basic rule of thumb. Is that every major 'page' of an application gets it's own view controller. What I mean by that is that during the wire framing stage of application design, everything that exists as its own entity will eventually be managed by its own View Controller. If there is a modal screen that slides over an existing screen, I will consider that to be a separate 'page' and give it its own view controller. If there is a view that overlays and existing page (such as a loading screen or help popup.) I would treat those differently, implement them as UIView subclasses and keep the logic in that 'pages' view controller. It the popup has behavior I will communicate back to that pages View Controller using the delegate pattern.
I hope this helps. It is very much a philosophical and architectural question and much could be written about it.
I use UIViewController whenever a view is full screen and either has outlets/actions and/or subviews.
Put everything on a screen into a UIViewController until the view controller starts to have too much code, then break out the screen into multiple UIViewControllers contained by one master view controller...
To put that into context of your answer, make a view controller for that modal overlay. It will have one anyway if you are using a nav controller to present it (and you probably should).
I have a somewhat different approach:
Override UIView if you plan to do custom drawing in drawRect. Otherwise, subclass UIViewController and use [self.view addSubview: blah] to add the components of the page.
There are a few other special cases, but that handles about 95% of the situations.
(You still will often need a UIViewController with a custom UIView. But it's common to have a custom UIViewController with no corresponding custom UIView.)
Is the thing that slides in a self contained screen? I mean, does it directly interact with the parent? If so, make it a UIView, if not, probably recommend a UIViewController.
A UIView is part of the UIViewController see the view property of UIViewController for this. As you pointed out correctly UIViewController manages a complete screen and there should be only one visible UIViewController at a time. But in most cases you will have more UIViews or subclasses of UIView visible on the screen.
The example you gave would be a correct use in most cases. As you may have noticed you will get a lot of functionality when subclassing the UIViewController. Animating the appearance and dismissal of the UIViewController would be one of them.
As marcc pointed out if the thing you want to slide in is not a self contained screen you would be better off using a UIView.
As a conclusion I would say that if you want to use the functionality that comes with subclassing UIViewController than go for it make it a UIViewController. Otherwise a UIView might be better.
The itunes U Standford class has a great lecture on UIViewControllers I would recommend watching it, because it has a lot of information regarding UIViewControllers in general.
If you are familiar with the MVC pattern, then you should be able to understand the difference between UIVIew and UIViewController. To make a simple statement, UIView is for rendering UI elements on screen. UIView is the superclass of pretty much all Cocoa Touch UI elements. Those elements do not know what information they are supposed to display, what they should do when a user clicks a button, what happens when an async network request is completed and so on. UIViewController is for all that and more. The view controller is responsible for placing the UI elements in the correct locations on screen, setting the contents of the UI elements, handling button presses and other user inputs, updating the model when needed etc.
Conceptually, a single UIViewController controls the contents of the whole screen in an iPhone App and that is why it is often easy to think of things in terms of view controllers. If you need a view where the user can select ingredients for a food recipe, you'll need a UIViewController for that. I made this distinction for myself because coming from a Java background I wasn't used to the framework enforcing MVC. I would think of things in terms of UIViews, and start implementing them that way and then run into all sorts of trouble because of that. If you are going to stick to UIKit for your App, then the workflow Apple has made for you is: for each separate view in your App, create a UIViewController subclass and then use Interface Builder to place the UI elements and to create connections for buttons etc. It works wonders, saves a ton of time and lets you concentrate on making your App function well.
I use UIViewController for showing View on full Screen.
For better control on custom view I prefer subclass of UIViewController instead of UIView, earlier I was using UIView for making custom sub class.
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. :)