Suppose I have tested a navigation controller at the root level.
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
self.window.rootViewController = self.navController; //iOS4
}
Now I want to move it into one tab inside a tab controller (in a larger app). I feel I should be able to do an equivalent to the above:
self.rootViewController = self.navController; //pseudocode
inside say
- (void)viewDidLoad
just dropping the code in. What in fact is the correct/elegant way to go about this? Often books refer to the root controller of a controller, and I'm wondering how to access it as I can the one of the window. (It's possible my understanding of this is misplaced of course).
Revised answer:
You can indeed insert your main UIViewController's view elsewhere in the UI as desired.
The trick is to do it early enough, and/or in the correct fashion. viewDidLoad isn't the correct place -- by the time that method is called, you usually want your main UIViewController implementation(s) to be in place.
There are two very good places to do this sort of UI setting up:
1) Via interface builder (nib files)
2) By implementing loadView in your UIViewController (do read the Apple docs -- very good info there).
It's one or the other, don't try do both at the same time for one UIViewController!
So in your example, you could use interface builder to edit a nib containing a tab bar controller. Drag in a plain UIViewController as a child of the tab controller, and then edit the 'Class' property of the UIViewController to MySpecialViewController (i.e. your subclass of UIViewController). And that's it, your nib now causes your view controller to be added to the UI inside a tab.
To taking approach 2), you'd want to set the viewControllers property of the UITabBarController in your loadView method of the UIViewController that contains the tab bar controller UI.
(My original answer below)
There isn't a consistently named way to access the root view controller(s) across Apple's provided view controllers (such as UINavigationController etc.). But the docs give you the info about how to access them; e.g. the UINavigationController has a property called viewControllers which you can access.
Ripping out existing view controllers willy-nilly and inserting them in different places in your UI is really not a very good idea. You could end up with all kinds of grief if you go down that round (e.g. device orientation changes). It's not the sort of approach Apple want you to take.
A better approach is to have several different instances of the view controller you want in different places. If they need access to the same data, they share the same data source. But they're individual, different instances.
Related
I have a tab bar as a root view controller each tab is a navigation controller with detail view controller as a root.
I need several tabs that have almost same logic but separate values for properties. I thought the best way is to use the same vc and set properties in the init method. Something like:
[[MyViewController alloc] initWithStyle:MyCustomStyle]
But I don't see where to call this method.
I assume you're creating your UI using a nib or a storyboard. If so, yes, doing custom initialisation of UIViewControllers or subclasses isn't supported -- the designated initialiser is called for you.
Possible workarounds:
create your UITabBarController programmatically: this way you can instantiate your VCs any way you like
have MyViewController find out which instance it actually is upon instantiation and set itself up accordingly -- by, for example, looking at its containing view controller and poking around with that (e.g. finding out what tab index it is)
The first option is the far nicer one, since it's cleaner and doesn't involve MyViewController having to know about its potential placement in a UITabBarController or similar (which is very hacky).
If you are relying on NIBs to instantiate and initialize your nav controllers, you can use several tricks to tell each one what it is.
Simpliest one is to tse Tag in NIB, and handle different values in initWithNibName:bundle:, initWithCoder:, or viewDidLoad
I'm not going to say this is good design by any means. I'm kind of inheriting something that is existing. Anyway, there is a TabController. One this one tab, there are two views that get loaded on demand based on a UISegmentController. Both of these two ViewControllers are subclasses of another ViewController that has methods I need.
When I'm in the TabController, I want to create a method that uses some of the superclass methods of the two ViewControllers. How do I get access to the tab's current ViewController since it's loaded on demand? Do I need to have a reference of the base controller type, and just have it set to the current view controller when it gets loaded on demand? Thanks.
To get the active tab showing on the screen, you could use [self.tabBarController selectedViewController] which will give you a UIViewController Reference. If you want to use the methods then you can cast it to your ViewController Superclass and then call methods on it like so (where self.tabBarController is your Tab Bar Controller):
MySuperClassViewController *viewC = (MySuperClassViewController *)[self.tabBarController selectedViewController];
[viewC someMethodDeclaredHere];
First, a little bit of terminology to clear up some potential confusion: I assume you mean a UITabBarController which controls various view controllers. Also, it seems you are using a UISegmentedControl which is not a view controller but a subclass of UIView. I hope these are just typos and not conceptional problems.
There is still some ambiguity about what you mean with "when I am in the tab controller". I assume you want to put code into the class representing the UITabBarController. But why? Just put these methods into the appropriate view controller, or if it is something that has to be done before, into your app delegate. However, if these methods are in a view controller superclass, they should have something to do with the logic necessary for this view controller.
If you need the methods elsewhere, i.e. outside your view controllers, consider creating a separate #include file and putting the methods there. Alternatively, you can use your app delegate which is conveniently accessible through [[UIApplication sharedApplication] delegate].
What I am trying to learn is what is the difference between the window, and viewcontroller. I know that you can only have one window per application. But you can have multiple viewcontrollers. When I create a project thats based off of the window-based application template I get only a window. I can create my own viewcontrollers, but I also know that I can put things directly onto the window in a window-based application (I think correct me if I am wrong). But with a view-based application I of course get a view that I can add things to. Can anyone clarify any of this for me if I make any sense?
Basically you have one instance of UIWindow that's hosting all your UIViewControllers and UIViews as part of the view hierarchy. That's why UIApplication has a call [UIApplication sharedApplication].keyWindow, to access the "root" view.
So for example you can have the following stack:
[UIApplication sharedApplication].keyWindow ->
MyView ->
MyOtherViewController
For more information, read up the Developer Documentation on UIWindow and UIViewController which provide a great explanation on the differences.
They're two completely different things. A window is a view that's special mainly in that it doesn't have a superview; it's the container that holds all other views. Because of its position at the root of the view containment tree, a window helps in the process of dispatching events to the proper views and redrawing as needed. Other than that, the window doesn't worry too much about the views it contains.
A view controller is not a view at all. It's a controller that manages a view and all its subviews. A view controller typically responds to user input from controls, populates views with the data they need, manages visibility of subviews, etc. The view that a view controller manages is installed in a window when that view controller is active, but the view controller generally doesn't keep a direct reference to the window itself.
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.
Situation:
I have an Xcode project based on the "Navigation-Based-Application" template. So that means I have a SINGLE UINavigationController that manages a UIViewController.
What I want To Do:
What I want to do is add one more UINavigationController to my project -- and be able to switch back and forth between them. (I want to do this to make space for some seperate unrelated content so that it does not have a back button pointing back to the root view controller.)
Question:
How do I add one more UINavigationController to my project and switch between the two UINavigationControllers?
The most common, and natural iPhone OS, way of doing this is to add a UITabBarController to your application. The Xcode template Tab Bar Application will guide you in the right direction on how to use it.
But...
If you don't like to have a Tab Bar in your application, and wish to switch between different UINavigationController instances (or any UIViewController for that matter), you can do something like this.
First you need to create your UINavigationController instances in a appropriate place (for example a new view controller, or in you Application Delegate, if you want to take the easy way out). You can then switch between controllers by just swapping which Navigation Controller's view that should be visible.
Example in the Application Delegate, "firstNavigationController" and "secondNavigationController" are UINavigationController instance variables:
- (void)showFirstNavigationController {
[secondNavigationController.view removeFromSuperview];
[self.window addSubview:firstNavigationController.view];
}
This will simply display the first instead of the second Navigation Controller. Note that this example is very simple. I didn't take into consideration that you should correctly handle the methods viewWillAppear:, viewDidAppear: and so on.