My controller hierachy:
TabBaseController (UITabBarController)
SubclassedController
In my tabbasecontroller I have a navigation bar button, which flips the subclassedcontroller with the presentModalViewController method, to a second UITabBarController.
So my question is: why does not
self.parentViewController
work in the second UITabBarController? It is nil.
I am trying this in my viewDidLoad method in the second UITabBarController:
if (self.parentViewController == nil) {
NSLog(#"Parent is nil");
}
UPDATED
This is the method in the UITabBarController with the navigationItemButton that presents it
-(IBAction)openModalTabController:(id)sender {
if (self.nvc == nil) {
ModalTabController *vc = [[ModalTabController alloc] init];
self.nvc = vc;
[vc release];
}
[self presentModalViewController:self.nvc animated:YES];
}
This is the controller(UITabBarController) that I present modally:
Header:
#interface NewBuildingViewController : UITabBarController {
}
#end
Main:
#implementation NewBuildingViewController
- (id)init {
[super initWithNibName:nil bundle:nil];
ViewController1 *vc1 = [[ViewController1 alloc] init];
ViewController2 *vc2 = [[ViewController2 alloc] init];
ViewController3 *vc3 = [[ViewController3 alloc] init];
NSArray *controllers = [[NSArray alloc] initWithObjects:vc1, vc2, vc3, nil];
[vc1 release];
[vc2 release];
[vc3 release];
self.viewControllers = controllers;
[controllers release];
self.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
return [self init];
}
#end
I also want to add that this message is displayed in the console(warning) when flipping:
Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
Using two-stage rotation animation is not supported when rotating more than one view controller or view controllers not the window delegate
It would be helpful if you were to show how you are presenting that second UITabBarController. Are you perhaps ignoring the following warning found in the UITabBarController class reference?
When deploying a tab bar interface, you must install this view as the root of your window. Unlike other view controllers, a tab bar interface should never be installed as a child of another view controller.
Related
I've been stuck trying to puzzle this out for a couple days now, and I'll admit I need help.
The root view controller of my application is a tab bar controller. I want to have each tab bar a different navigation controller. These navigation controllers have completely different behavior.
So how do I set this up in terms of classes? Per Apple's documentation, I'm not supposed to subclass UINavigationViewController. So where do I put the code that drives each of these navigation controllers? Does it all get thrown in App Delegate? That would create an impossible mess.
This app should run on iOS 4.0 or later. (Realistically, I can probably require iOS 4.2.)
This is taken from one of my applications. As you say, you are not supposed to subclass UINavigationController, instead you use them as they are and you add viewcontroller on the UINavigationController's. Then after setting the root viewcontroller in each UINavigationController, you add the UINavigationController to the UITabBarController (phew!).
So each tab will "point" to a UINavigationController which has a regular viewcontroller as root viewcontroller, and it is the root viewcontroller (the one you add) that will be shown when a tab is pressed with a (optional) navigationbar at top.
UITabBarController *tvc = [[UITabBarController alloc] init];
self.tabBarController = tvc;
[tvc release];
// Instantiates three view-controllers which will be attached to the tabbar.
// Each view-controller is attached as rootviewcontroller in a navigationcontroller.
MainScreenViewController *vc1 = [[MainScreenViewController alloc] init];
PracticalMainViewController *vc2 = [[PracticalMainViewController alloc] init];
ExerciseViewController *vc3 = [[ExerciseViewController alloc] init];
UINavigationController *nvc1 = [[UINavigationController alloc] initWithRootViewController:vc1];
UINavigationController *nvc2 = [[UINavigationController alloc] initWithRootViewController:vc2];
UINavigationController *nvc3 = [[UINavigationController alloc] initWithRootViewController:vc3];
[vc1 release];
[vc2 release];
[vc3 release];
nvc1.navigationBar.barStyle = UIBarStyleBlack;
nvc2.navigationBar.barStyle = UIBarStyleBlack;
nvc3.navigationBar.barStyle = UIBarStyleBlack;
NSArray *controllers = [[NSArray alloc] initWithObjects:nvc1, nvc2, nvc3, nil];
[nvc1 release];
[nvc2 release];
[nvc3 release];
self.tabBarController.viewControllers = controllers;
[controllers release];
This is how I go from one viewcontroller to another one (this is done by tapping a cell in a tableview but as you see the pushViewController method can be used wherever you want).
(this is taken from another part of the app)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.detailedAnswerViewController == nil) {
TestAnsweredViewController *vc = [[TestAnsweredViewController alloc] init];
self.detailedAnswerViewController = vc;
[vc release];
}
[self.navigationController pushViewController:self.detailedAnswerViewController animated:YES];
}
The self.navigationcontroller property is of course set on each viewcontroller which are pushed on the UINavigationController hierachy.
My UITabBarcontroller has two view controllers
-Favorites
-Keypad
I add these two controllers in an array in the order Favorites,Keypad,nil.
When the app is launched only the Favorites tab appears in the tabbar, I have to click the second tab for the "Keypad" (Title of the viewController) text to appear on the tabbar.
How do I make the tabbar have the title of both the view controllers at startup itself?
show us your tabbarcontroller init method. It should be straight forward -
-create 1 tabbarcontroller
-create 2 tabbaritems
-create 2 nav controller
-assign tabbaritems to nav controllers, using navcontroller.tabBarItem property
-use tabbarcontroller setViewControllers:animated: function to add nav controllers to tabbar, then add tabbar controller to window.
Try this in your ViewController where you load the tabController:
-(void)viewDidLoad
{
[super viewDidLoad];
// creating the tabController
UITabBarController *tabBarController = [[UITabBarController alloc] init];
NSArray* controllers = [NSArray arrayWithObjects: myViewController, nil];
myViewController.title = #"Title";
tabBarController.viewControllers = controllers;
[controllers release];
[self.view addSubview:tabBarController.view];
}
try this instead:
-(id)setup
{
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:#"xxxx" image:[UIImage imageNamed:#"xxx.png"] tag:0];
self.tabBarItem = item;
[item release];
return self;
}
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self setup];
}
return self;
}
I want to use a modal view (UIViewController) as a "normal" view, which can be pushed on the navigation controller stack. Normally, a modal view is presented like this:
LoginViewController *myView = [[MyViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myView];
[self.navigationController presentModalViewController:navController animated:YES];
[myView release];
myView = nil;
[navController release];
navController = nil;
But I want to do something like this:
[[self navigationController] pushViewController:myView animated:YES];
The problem is that my modal view has a right and a left button. So I would have to check how the view is loaded and present the buttons in another way. The idea behind this is to have the back button. So I can use the same modal view a few times.
Edit:
#petert:
Now I followed your example. My issue is that I'm using a UINavigationBar for the modal view. To get this UINavigationBar I create a navigation controller. I'm using the navigation bar because I have my buttons in it. So checking if parentViewController is of type UINavigationController does not work for me. I'm always getting a modal view. Here is how I do it:
// load modal view
MyViewController *myView = [[MyViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myView];
[[self navigationController] presentModalViewController:navController animated:YES];
[navController release];
navController = nil;
[myView release];
myView = nil;
// load as normal view
MyViewController *myView = [[MyViewController alloc] init];
[[self navigationController] pushViewController:myView animated:YES];
Good tips in this StackOverflow answer.
I prefer to use UIViewController's property:
#property(nonatomic, readonly) UIViewController *parentViewController
in a view controller's subclass:
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 last view controller.
So in -viewDidLoad for example:
- (void)viewDidLoad
{
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
{
// navigation controller
self.title = #"...";
}
else
{
// modal
self.title = #"Modal";
// add cancel and done buttons now...
}
}
Or, a pretty simple solution would be to customize your init method to your MyViewController class to encode your intent for the view controller.
Add the following to the MyViewController header:
#interface MyViewController : UIViewController
{
BOOL modal;
}
- (id)initForModal:(BOOL)isModal;
#end
Now in the implementation file:
#interface MyViewController ()
#property (nonatomic) BOOL modal;
#end
#implementation MyViewController
#synthesize modal;
- (id)initForModal:(BOOL)isModal;
{
if (self = [super initWithNibName:#"MyViewController" bundle:nil])
{
self.modal = isModal;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.modal)
{
// add cancel and done buttons …
}
else
{
// assuming we're presented from a navigation view …
}
}
Now to use this modally:
// load modal view
MyViewController *myView = [[MyViewController alloc] initForModal:YES];
Or not modally:
// load as normal view
MyViewController *myView = [[MyViewController alloc] initForModal:NO];
I'm assuming you're creating the view controller(s) from NIBs, but as always see the View Controller Progamming Guide for iOS and especially the section titled "Defining a Custom View Controller Class".
For clarification: myView isn't modal. You just present it as a modal one.
If you just push it into a UINavigationController hierarchy it will behave like a "normal" one.
You can't push the same view controller onto the navigation stack several times. Just once.
Also see this for how to customize the view:
SO modal question
here's my code:
ViewController *vc = [[ViewController alloc] initWithNibName:#"TableView" bundle:nil];
[self.navigationController presentModalViewController:vc animated:YES];
//[self setView:[vc view]];
If I call it, nothing happens. However, if I change it to:
ViewController *vc = [[ViewController alloc] initWithNibName:#"TableView" bundle:nil];
//[self.navigationController presentModalViewController:vc animated:YES];
[self setView:[vc view]];
The view appears just fine (without the transition, of course).
What am I doing wrong? Is there anything special you have to take care of when initializing the view controller? I tried to copy as much as possible from Apple's examples, but I can't get this to work...
Thanks for any input!
-- Ry
You can only present modal view controllers from controllers that have already been shown onscreen (usually through a UINavigationController or UITabBarController). Try creating a UINavigationController, pushing a viewController to it, and then presenting your modal controller. There's a starter project in Xcode that shows how to create a UINavigationController-based flow if you're unfamiliar with it.
One other thing to note: if you haven't pushed the view controller onto a UINavigationController, the .navigationController property will be nil, and messaging it will have no effect.
I encountered the same problem while attempting to show a modal view over another modal view. Ben's answer is correct, and can be implemented like so:
#interface FirstView: UIViewController {
UIViewController *firstView;
}
- (IBAction)showOptionsView:(id)sender;
#end
In the main view class:
- (void)viewDidLoad {
[super viewDidLoad];
firstView = [[UIViewController alloc]init];
[firstView setView:self.view];
[firstView setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
}
- (IBAction)showOptionsView:(id)sender {
OptionsView *optView = [[OptionsView alloc]initWithNibName:#"OptionsView" bundle:nil];
if(firstView != nil) {
[firstView presentModalViewController:optView animated:YES];
[optView release];
}
I'd like to launch a modal view controller the way one does with 'ABPeoplePickerNavigationController' and that is without having to creating a navigation controller containing the view controller.
Doing something similar yields a blank screen with no title for the navigation bar and there's no associated nib file loaded for the view even though I am invoking the initWithNibName when the 'init' is called.
My controller looks like:
#interface MyViewController : UINavigationController
#implementation MyViewController
- (id)init {
NSLog(#"MyViewController init invoked");
if (self = [super initWithNibName:#"DetailView" bundle:nil]) {
self.title = #"All Things";
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"All Things - 2";
}
#end
When using the AB controller, all you do is:
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];
ABPeoplePickerNavigationController is declared as:
#interface ABPeoplePickerNavigationController : UINavigationController
The other way to create a modal view as suggested in Apple's 'View Controller Programming Guide for
iPhone OS':
// Create a regular view controller.
MyViewController *modalViewController = [[[MyViewController alloc] initWithNibName:nil bundle:nil] autorelease];
// Create a navigation controller containing the view controller.
UINavigationController *secondNavigationController = [[UINavigationController alloc] initWithRootViewController:modalViewController];
// Present the navigation controller as a modal view controller on top of an existing navigation controller
[self presentModalViewController:secondNavigationController animated:YES];
I can create it this way fine (as long as I change the MyViewController to inherit from UIViewController instead of UINavigationController). What else should I be doing to MyViewController to launch the same way as ABPeoplePickerNavigationController?
I'd like to launch a modal view controller the way one does with 'ABPeoplePickerNavigationController' and that is without having to creating a navigation controller containing the view controller
But this is exactly what ABPeoplePickerNavigationController is doing. It isn't magic, it is a UINavigationController that instantiates a UIViewController internally (a UITableView that is populated with your address book contacts) and sets the UIViewController as its root view.
You can indeed create your own similar UINavigationcontroller subclass. However, within it's initializer, you will need to create a view controller to load as its root view just like ABPeoplePickerNavigationController does.
Then you can do what you are trying like this:
[self presentModalViewController:myCutsomNavigationController animated:YES];
In the code you posted:
#interface MyViewController : UINavigationController
#implementation MyViewController
- (id)init {
NSLog(#"MyViewController init invoked");
if (self = [super initWithNibName:#"DetailView" bundle:nil]) {
self.title = #"All Things";
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"All Things - 2";
}
#end
I suspect you are having NIB issues. there isn't a "rootViewController" outlet to connect. This is why you have a blank screen.
The initalizer you should be using internally is this:
self = [super initWithRootViewController:myCustomRootViewController];