How does Apple make its controllers contain other controllers? - iphone

The Apple documentation gives the following warning regarding using View Controllers to manage part of a screen.
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.
Now the strange thing is that Apple breaks this advice. UITabBarController, UINavigationController, UISplitViewController all go against this advice. There is a discussion on the Apple forums about what can go wrong if you ignore this advice.
I found a tutorial on how to do this, but the method had a problem with overlapping the status bar which had a fix that seemed kind of dodgy. Other questions have solutions that appear kind of dodgy or advise against doing it.
So given the question is what method does Apple use for its own controllers?

They also say not to use autorelease pools, but there are autorelease statements throughout their samples. Stick with what's practical. Purity should probably be slightly secondary.

Apple wrote UIKit, so they can do what they like.
There's a lot of stuff happening under the hood:
view{Will,Did}{Appear,Disappear}
View rotations (ugh, headache)
UIViewControllerWrapperView, which is sometimes the parent of UIViewController.view. Or something.
UIViewController.navigationController/tabBarController/parentViewController/modalViewController
Popovers are weird. I'm not sure how they fit in.
If you write your own views, you can probably get away with using UIViewController to control them, but don't expect all the magical behaviour that UIKit gives to a "proper" view controller.
EDIT: I probably shouldn't StackOverflow when it's late. I really mean something like this:
If a view is controlled by a UIViewController, the view controller should exist in the view controller hierarchy (i.e. functions like presentModalViewController:animated:). This allows UIKit to handle the complicated bits.
When you use something like [fooSubview addSubview:viewController.view], UIKit may not do all the things it's supposed to do. What retains viewController? What happens if there's a memory warning and fooSubview gets unloaded?
If you set something like viewController.view.frame = (CGRect){{0,0},{320,480}}, you're asking for trouble too: UIViewController sets the frame depending on the current status/navigation/tab/etc bar. It might re-set it, or it might use the frame to decide how to lay out view controllers that you push on top (I've noticed this behaviour; it's messy). If you change viewController.view.transform, strange things can happen on view rotations, since the view transform is what UIViewController uses for orientation (along with the status bar and a pile of other things).
There's only one well-supported exception I know of:
[window addSubview:viewController.view];
[window makeKeyAndVisible];
(In fact, you can stick viewController.view inside a full-window view inside the window; I'm not sure how that works.)
I think in OS 4.0+ you're supposed to set window.rootViewController = viewController instead.

Related

How do I switch ViewController instead of adding to stack?

I have a number of objects linking to each other in a circle. Each object holds a reference to it's neighbors, square and triangle are different class types:
The tow classes, triangle and square, are made visible with a ViewController, and they link to each other with segues.
So far, no problem. However, while I'm browsing around in my structure, I keep adding ViewControllers on top of each other. Not only does this seem a bad practice memory-wise, but it also presents the problem that when I want to quit this structure, I have to back-track by closing all ViewControllers that I opened.
So what I'm looking for, is a way to not add the next ViewController on top of the current on in the stack, but to replace the current ViewController with the next one.
I've been looking for a while for a solution, but have little success. So I feel that doing what I want is either impossible, or I'm just not getting an obvious point and don't know what to look for. Do I need a RootViewController for something like this? Or should I create a custom segue that dismisses the old ViewController before adding the new one? I'm really at a loss here.
Add all subView once , in viewDidLoad and give tags to all you SubView after that where you you want to show that view in viewController don't add it just bring it to front by calling the function [[self.view viewWithTag:1]bringToFront]
Since you are probably using a navigationcontroller, you should have a look at the UINavigationController reference. There are methods to modify the navigation stack. You cannot do this only with a storyboard. You will need some custom code.
You should have a look at the UINavigationController method setViewControllers:animated:.
Sounds like you want to do one of a couple things
Replace your rootViewController
Have a rootViewController which acts as a container for a single UIViewController and change that out as needed. In iOS 5 you can do this with a custom UIViewController, but or you could use one which Apple provided.UINavigationController` can do it, but unless you are also using it to navigate a tree like structure of view controllers it's probably not the best option.
Your best answer sort of relies on your need.
If your controllers don't have much state or don't get swapped in and out a lot you could use option 1.
If you expect users to swap between controllers often and quickly and/or your controllers require significant setup or have a lot of state then you might want to a NSArray and just use presentViewController:animated:completion: to show different controllers when needed. Storing your controllers in a NSArray has the added bonus of easily being able to identify their neighbors.
The UINavigation controler's method setViewControllers is an option.
Another would be to pop the most rencent view controller using popViewControllerAnimated:
In some cases popToRootViewControllerAnimated: would be best or even popToViewController:animated:. Hoever, I personally was not successful using popToViewController:animated: but that may have been my fault at that time.
Yes, I think you need a root view controller. I myself tried to exchange the root view controller the other day but failed doing so. In the end it was probably not the most elegant solution but easier for me to implement some dummy root view controller which does nothting but display my app logo in the background (Same as the default image but moved into negative coordinates in order to match the default image on startup. It is laying 'behind' the navigation bar and status bar.). It could show some empty black background or so. In the end it is will most probably never be visible.

Switching views for iphone application - is this the right way?

After looking for many hours to find a working view switching code, I finally found a great tutorial on YouTube. It was exactly what I needed as I needed to switch views when buttons are pressed.
I just wonder if the techniques used in that video are valid. The used code to switch screens is
viewsViewController *second = [[viewsViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:second animated:YES];
Where viewsViewController (or any class that is used there) is a class that's a subclass of UIViewController. This class is made by clicking File > New File > Add UIViewController subclass.
Is this method according to the Apple guidelines? Is this method memory friendly?
I sure hope the technique is valid. All other examples contained too little information so I couldn't make the example to work. And this is very stylish and short code which works .
That's one way to do it. When you present a view controller modally, there's some expectation that the view controller will go away at some point and the user will return to the parent view controller.
Another option is to employ a UINavigationController, to which you can send -pushViewController:animated: messages.
Yet another way is to let a UITabBarController switch view controllers for you when the user hits the corresponding tab.
One more: you can set the window's rootViewController property, at which point the window will add the view controller's view as a subview of itself.
Your best bet is to read through the View Controller Programming Guide for iOS.
This is one way of doing it. Be aware of some issues. Both views will be in memory, which is okay if they are a reasonable size. The modal view animates above the parent.
There are other models that you can use for simple navigation, if that's your main goal. I would suggest looking at UINavigationController and see if that might better meet your needs. It is probably the most common method to navigate views and provided a lot of the foundation of view management saving you that effort.

should uiviewcontrollers always be part of the viewcontroller hierarchy?

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.

UISplitViewController and complex view hierarchy

I'm doing an iPad tech demo and I'm running into a serious technical problem.
I have an app concept that leverages UISplitViewController, but NOT as the primary controller for the entire app.
The app flow could be described roughly as this:
Home screen (UIViewController)
List->Detail "Catalog" (UISplitViewController)
Super Detail Screen (UIViewController but could conceivable also be a child of SplitView).
The problem is in the flow between Home and Catalog. Once a UISplitViewController view is added to the UIWindow, it starts to throw hissy fits.
The problem can be summarized at this:
When a UISplitView generates a popover view, it appears to then be latched to its parent view. Upon removing the UISplitView from the UIWindow subviews, you will get a CoreGraphics exception and the view will fail to be removed.
When adding other views (presumably in this case, the home screen to which you are returning), they do not autorotate, instead, the UISplitView, which has failed to be removed due to a CG exception, continues to respond to the rotation instead, causing horrible rendering bugs that can't be just "dealt with". At this point, adding any views, even re-adding the SplitView, causes a cascade of render bugs.
I then tried simply to leave the SplitView ever present as the "bottom" view, and keeping adding and removing the Home Screen from on top of it, but this fails as SplitView dominates the Orientation change calls, and Home Screen will not rotate, even if you call [homeScreen becomeFirstResponder]
You can't put SplitView into a hierarchy like UINavigationController, you will get an outright runtime error, so that option is off the table. Modals just look bad and are discourages anyway.
My presumption at this moment is that the only proper way to deal with this problem is so somehow "disarm" UISplitViewController so that it can be removed from its parent view without throwing an unhandled exception, but I have no idea how.
If you want to see an app that does exactly what I need to do, check out GILT Groupe in the iPad app store. They pulled it off, but they seem to have programmed an entire custom view transition set.
Help would be greatly appreciated.
Apple states:
The split view controller’s view
should always be installed as the root
view of your application window. You
should never present a split view
inside of a navigation or tab bar
interface.
This does mean it should be root view and not subview of another view. Even though they add:
You should never present a split view inside of a navigation or tab bar interface
That does not mean you can add it as a subview of any other controller either. (sorry)
I have a feeling that what you are experiencing is the byproduct of trying to do so. I am actually surprised that GILT Groupe's app did not get rejected. Apple has a tendency to enforce these HIG guidelines rather strictly lately. They (as you found out already) cause a rather nasty runtime error when you attempt to add them to a NavigationController.
I've solved this for myself... actually worked around... by presenting all other possible full screen views as modals of the SplitView...
This is an unsavory way of doing things in my book, but Apple leaves you little choice if you want to leverage a SplitView only "sometimes" within an app.
I had some success by creating a second UIWindow. I associate the UISplitViewController with that, and switch it out with the main window when I want to show the splitview. It seems to work they way I wanted, except for a slight delay in rotations and a log message about "wait_fences".
Unless your developing for jail-broken devices then bending apples rules/wishes isn't a good idea. Like Jann and Jasconius state above this means keeping a splitView controller view root, not over-using modals (vague) and not using multiple windows.
Also, the Gilt app is only available in the US
I'v been trying to find a solution too and have ended up programatically removing views from the window like Tuannd talks about but the landscape rendering bug is unforgivable.
#Jasconius, What is the max number of modals are you are presenting at any time?
I am struggling with this same issue. I've been trying various things poking at the UISplitViewController as a black box and see how it reacts.
I seem to have come up with a solution to my case which seems to be working satisfactorily.
The key appears to be that the first view added to the UIWindow is the only view properly initialized. All the problems I've had tend to stem from incorrect notification of the orientation of the device. The first view added, apparently has this correctly configured.
In my case I didn't want the UISplitView as the first view. The following is working for me.
The app delegate application:didFinishLaunching method is special. Adding the view to the UIWindow must occur here. If it is done elsewhere it does not get configured properly.
Essentially the magic sauce, is have the split view be the first view added to the window. Its then ok to remove it as long as you retain the UISplitViewController. From then on you can swap other views in and out, including the UISplitView and most things seem to be ok.
I've still run into a few issues. Popovers on views other than the split view are confused on the views frames and location of toolbar buttons and will display in the wrong location. I place then in a specific location and that seems to handle that case.
If a popover on the split view is still displayed, and you try to view another view, the orientation of the second view is confused and shows up sideways. If that view is accessed before the popup is displayed, all is well. I've fixed this my manually dismissing the popover before switching to any other view.
Here's the code if it helps. All the controllers are instance variables of appDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// This also seems to work as good magic. Seems to set orientation and size properties that persist.
[window addSubview:splitViewController.view];
[splitViewController.view removeFromSuperview];
[self switchToNewViewController:firstController];
[window makeKeyAndVisible];
return TRUE;
}
- (void)switchToNewViewController:(UIViewController *)newViewController {
[popoverController dismissPopoverAnimated:FALSE];
if (newViewController != currentViewController) {
[currentViewController removeFromSuperview];
currentViewController = newViewController;
[window addSubView:newViewController.view];
}
}
Just wanted to say that I was running into these same issues, found this forum topic, and followed the advice from g051051 above. This is working perfectly for me. I am not seeing any glitch, and no messages about wait_fences in the console of the device.
I simply used IB to create two UIWindow objects in the main XIB, created as normal the UISplitViewController and then also an instance of my other controller derived from UIViewController (which I am using for full screen display). I have simply hooked them up by attaching the rootViewController for each UIWindow to its appropriate controller.
In application:didLaunch...: method I can decide which window to send the makeKeyAndVisible method and which to set to hidden. When the user want to switch back and forth I simply have to send makeKeyAndVisible to one and set the hidden property on the other, that's all there is to it.
As indicated all of the rotation related messages are sent to each controller appropriately, regardless of which one is associated with the currently visible window.
Anyway, works great for me, and actually quite easy to set up.

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. :)