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].
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
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.
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.
I'm trying to find the best way to refactor this. Right now, each of the view controllers (about 20 of them) have a function that initializes the content of the toolbar / navigation bar like so ie,
-(void)toolbarInit
and on each of the controller's viewDidLoad, you will see that the function is being called ie, [self toolbarInit];
Basically what that toolbarInit does is to put a loginButton on the navigation bar's rightBarButtonItem.
Should I:
a. put the toolbar/nav bar setup in the app delegate ( is it possible to initialize each of the view controller's nav bar no matter how deep down the stack it is already?)
b. create a toolbarController or something and just put all the setup code/login code over there?
Many thanks for any other suggestions.
I'd consider one of these options:
It sounds as though you're repeatedly pushing the same or similar UIViewControllers onto the stack. Is it possible they should all derive from the same UIViewController subclass? If so, the initialization could take place in a superclass shared by all of the 20 or so items in the stack.
If the view controllers have different superclasses, consider defining a category on UIViewController that handles the instantiation and addition of your buttons. Then you need only import the header and call the same [self toolbarInit] in viewDidLoad.
Same as above, except instead of using a category, create a standalone class for handling setup like this. The logic in toolbarInit could be stuck in a class method like setupNavigationItem:.
I have a UIViewController — let's call it "FormController" — which is simply a form that edits an object. I want to use it in 2 different situations:
Creating a new object — using using UINavigationController's presentModalViewController: method.
Editing an existing object — push the view controller onto the UINavigationController stack, not using a dialog method.
There is a slight difference in that in the modal situation I would like to have a toolbar with "Cancel" and "Done" buttons, whereas in the stack situation I would like to just have the navigation bar provided by the UINavigationController.
This would be similar to the Contacts application where the "New Contact" and the "Edit Contact" screens seem to use the same view controller, but the New Contact form is presented modally while the Edit screen is pushed onto the navigation stack.
My question is: What is the best way to handle both situations without having to write 2 separate, but mostly identical view controllers?
I thought about creating a "ModalFormController" which encapsulates the bare "FormController" through composition and adds a toolbar, but I read somewhere in the docs that Apple doesn't recommend nesting view controllers.
Why not use subclassing? Make ModalCreateFormController a subclass of EditFormController and handle the modal-specific stuff in the subclass.
What I do (sometimes) is set up an enum that specifies the type of the view controller.
For example, you might have two types: an Edit type, and an Add ("new") type.
The Add type is implemented via a modal view controller, while the Edit type is pushed onto an existing navigation stack.
In the view controller's -viewDidLoad: method, I simply do a switch/case tree that sets up the title and other appearance characteristics depending on the type enumeration specified above.
The nice thing about this is that it is easy to add a new type. The downside is that the conditional tree for handing this enumeration can get complicated quickly, depending on how different the types are.
But the switch/case tree makes it a lot easier to manage.
So, it depends on what you're trying to do with the two types. But it's definitely doable.
In addition to having an explicit property on the view controller (as Alex Reynolds suggests), two other approaches that occur to me are:
If you have some kind of model object that you're editing, ask it for its current state. If it's ever been saved, then you're in edit mode. Otherwise, you're in create mode.
Look at the value of the controller's parentViewController property. If it's an instance of UINavigationController, then you're in the navigation stack. If you're being displayed modally, it'll be an instance of your list controller.
Ug, I hate extra ivars…
I use this instead:
if([[self.navigationController viewControllers] objectAtIndex:0] == self){
//Modal
}else{
//Pushed
}
It is a bit of a hack, but we are using the logic that if the offending view controller is the first in the stack, you can't go back. Actually we are ignoring the fact of whether it is modally displayed at all.
I had to do this a bunch of times in my app and after trying a couple different ways of doing it, including modal subclasses & a re-usable modal helper classes that used forwardInvocation. I found the best pattern was to make a containingModalViewController method each view controllers that (usually) creates and returns a UINavigationController for the caller to use with presentModalViewController.
In most cases this method builds and returns a UINavigationController with self as the root view controller (with repeated calls to the method checking self.navigationController and returning that instead if it's not nil). Other cases I made a dummy root controller first and pushed self on second in order to get a back button. Then a trick can be used to catch the back button press: http://smallduck.wordpress.com/2010/10/05/intercepting-uinavigationcontroller/
In some cases the view doesn't need a navigation bar and so this method just adjusts some flags and returns self. I even found in some cases that did need a navigation bar it was simpler to make that method invoke self.view, then tweak the view hierarchy to add a UINavigationBar and again return self. But in any case, the setup often isolated to that one method, and the caller handles it the same in each case.
Apple explains how the contacts application works under the hood:
To allow a custom view controller class to be used to both display and edit content, override the setEditing:animated: method.
You get some functionality for free, e.g. Edit/Done button.