Having a UITabBar AND a UINavigationController in an app? - iphone

Hey everyone, I am new to iPhone development and I'm not understanding the whole UINavigationController and UITabBarController idea. Is one a substitute for the other - how do apps such as Tweetie combine both?
I'd like to have my app have a persistent Tab Bar # the bottom (which seems to be working), but also a Navigation bar at the top which can push/pop views onto the screen without removing the tab bar.
How can I accomplish this?
What should the hierarchy look like in IB as far as my MainWindow.xib with regards to all of these controllers?
What is best practice here?
Thanks very much,

Just wrap the view controller inside the UINavigationController and Place the UINavigationController inside the UITabBar.
This will work fine for you…
Example:
NSMutableArray *tabBarViewControllers = [[NSMutableArray alloc] initWithCapacity:2];
tabBarController = [[UITabBarController alloc] init];
[tabBarController setDelegate:self];
UINavigationController *navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:<Your View controller1>];
[tabBarViewControllers addObject:navigationController];
[navigationController release];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:<Your View controller2>];
[tabBarViewControllers addObject:navigationController];
[navigationController release];
navigationController = nil;
tabBarController = tabBarViewControllers;
[tabBarViewControllers release];
tabBarViewControllers = nil;

Use the wizard for a Tab Bar Application, and set it up as normal. In any tab where you want to add a navigation controller, create it in the XIB using the library. My XIB has:
- File's Owner DescriptiveNameNavViewController
- First Responder
- View UIVIew
- Navigation Controller UINavigationController
- Navigation Bar UINavigationBar
Note that there isn't anything in the view. See viewDidLoad below for where the UINavigationController gets attached to the UIView.
In the header file for the Tab's ViewController (which I've here called DescriptiveNameNavViewController -- there isn't a particular standard for this, but I use [Something]NavViewController to remind me that this ViewController contains a navigation controller with the navigation stack. This is the controller name that I set in the MainWindow.xib that the wizard generates) Set up a UINavigationController * IBOutlet that has the navigation controller in the XIB attached to it:
#interface DescriptiveNameNavViewController : UIViewController {
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
In the controller for the DescriptiveNameNavViewController , do something like this:
- (void)viewDidLoad {
[super viewDidLoad];
[[self view] addSubview:[navigationController view]];
DescriptiveNameController *aController = [[[DescriptiveNameController alloc ] initWithNibName:#"DescriptiveNameController" bundle:nil ] autorelease];
aController.title = #"Descriptive Title";
//
// app stuff goes here.
//
[self.navigationController pushViewController:aController animated:YES];
[self.navigationController setDelegate:self];
}
Setting the delegate in the DescriptiveNameNavViewController is super-important, because otherwise you won't get the methods called that you expect in DescriptiveNameViewController instances and anything else you push into the navigation controller's stack.
In DescriptiveNameNavViewController, implement the UINavigationControllerDelegate methods like this:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([viewController respondsToSelector:#selector(viewDidAppear:)]) {
[viewController viewDidAppear:animated];
}
}
And that will cause messages to get propagated to controllers inside the UINavigationController like you expect. It seems like many problems that people encounter are because the viewDidAppear: or other methods aren't getting called on the ViewControllers pushed into the NavigationController.
Anyway, let me know if more detail would help.

Related

iPhone Navigation Back Button

I am having issues with the back button not showing up on the SettingsViewController. The navigation bar does show up when the view is pushed, but no back button.
I am creating this inside a view controller, which is not a navigation controller. Any ideas or suggestions on what is actually going on here.
- (void)viewDidLoad
{
self.title = #"Settings";
}
- (IBAction)showSettingsModal:(id)sender
{
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:settingsViewController] autorelease];
[self presentModalViewController:navController animated:YES];
[settingsViewController release];
}
You are creating a new navigation stack. You will need to add your own Back button and set the action of that to a delegate method on the calling VC to dismiss it.
UPDATE:
There seems to be lots of confusion about where and how to dismiss ModalViewControllers. The wrong thing to do in most cases is to call the Dismiss method from the Modal VC itself if you are wanting the parent to act on that dismissal. Instead, use delegation. Here is a simple example:
ModalViewController.h:
#protocol ModalViewControllerDelegate
-(void)dismissMyModalVC;
#end
#interface ModalViewController : UIViewController {
id < ModalViewControllerDelegate > delegate;
}
#property (nonatomic, retain) id < ModalViewControllerDelegate > delegate;
// The rest of your class properties, methods here
ModalViewController.m
#synthesize delegate;
...
// Put in the Method you will be calling from that Back button you created
[delegate dismissMyModalVC];
CallingViewController.h:
#import "ModalViewController.h"
#interface CallingViewController : UIViewController
<ModalViewControllerDelegate>
// Rest of class here
CallingViewController.m:
ModalViewController *mvc = [[ModalViewController alloc] initWithNibName:#"ModalViewController" bundle:nil];
mvc.delegate = self
[self presentModalViewController:mvc animated:YES];
...
// The ModalViewController delegate method
-(void)dismissMyModalVC {
// Dismiss the ModalViewController that we instantiated earlier
[self dismissModalViewControllerAnimated:YES];
That way the VC gets dismissed properly from the controller that instantiated it. That delegate method can be modified to pass along objects as well (like when you are finished logging a user in, etc)
SettingsViewController does not have a back button because it is at the bottom of stack. If you want a button to dismiss the modal dialog, you will have to add it yourself.
you can try this
UIBarButtonItem * backButton = [[UIBarButtonItem alloc]initWithTitle:#"Back"style:UIBarButtonItemStylePlain target:self.navigationItem.backBarButtonItem action:#selector(dismissModalViewControllerAnimated:)];
You are presenting your new controller as modal view controller. Modal controller presents its topmost. You should:
[self.navigationController pushViewController:navController animated:YES];
to push view controller onto the stack, and then you will see Back button.
Read Apple documenation on presenting view controllers:
https://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html
EDIT Didn't see that the calling view controller is not part of the navigation controller. In that case, you will have to create back button manually, and set it as a left bar navigation item.

iPhone - pushViewController Issue

I have a root view controller which should load another view controller as soon as it is done loading (i.e. in the viewDidLoad method).
I am using the UINavigationController in order to push a new view controller onto the stack:
In my rootviewcontrollerappdelegate:
-(void) viewDidLoad{
LoginViewController* lvc = [[LoginViewController alloc]init];
[self.navigationController pushViewController:lvc animated:NO];
}
I have textfields and buttons in the view controller to be loaded. The above doesn't seem to work however...It loads just a blank grey screen and no UINavigation bar is present. If I comment out the second line (pushViewController line), then I see the navigation bar. So I think it is loading something, but the items in the view controller being loaded are not being shown...Any ideas why?
Check if navigationController is pointing to nil. If it does, try
[self.view addSubview:self.pushViewController.view]
I had the same problem and found the above solution here:
UIViewController -viewDidLoad not being called
Unless you're doing something tricky, you should be calling alloc on the LoginViewController class rather than a variable. Also, if you've set up LoginViewController in Interface Builder (as opposed to programmatically), you'll need to load it from an NIB:
LoginViewController *lvc = [[[LoginViewController alloc] initWithNibName:nil bundle:nil] autorelease];
[self.navigationController pushViewController:lvc animated:NO];
Have a look at initWithNibName:bundle: in the docs.
Not entirely sure what you are trying to achieve but when you instantiate LoginViewContoller it should probably look like this
LoginViewController* lvc = [[LoginViewController alloc]init];
Judging by the nature of your naming for your view controller, is your LoginViewController the first view controller for your UINavigationController?
If that is what you're trying to do, you should instead initialise your navigation controller with the LoginViewController as the root controller instead of pushing it onto the navigation stack.
UINavigationController has a method to do this:
- (id)initWithRootViewController:(UIViewController *)rootViewController
EDIT:
Well, one way you can go about it is like this.
In your application delegate .h file, you should have declared a UINavigationController.
#interface MyAppDelegate : NSObject <UIApplicationDelegate>
{
UINavigationController *navController;
}
#property (nonatomic, retain) UINavigationController *navController;
#property (nonatomic, retain) IBOutlet UIWindow *window;
#end
In your App Delegate didFinishLaunching:withOption: you can create an instance of your LoginViewController there, and use that to init your UINavigation controller as the root view controller
#import "LoginViewController.h"
#implementation MyAppDelegate
#synthesize navController;
#synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
LoginViewController *loginController = [[LoginViewController alloc] init];
navController = [[UINavigationController alloc] initWithRootViewController:loginController];
[loginController release];
[[self window] setRootViewController:navController];
[navController release];
[self.window makeKeyAndVisible];
return YES;
}
I probably have a typo here or there but that's one way I would go about doing it.

UINavigationController - basics

I'm trying to use a UINavigationController but I'm uncertain how. Up till now (for about a year), I've been using presentModalViewController and dismissModalViewController to present/dismiss view controllers.
So, this is what I did. My main view controller (the first one that shows on launch) is called MainViewController, and it extends UIViewController.
So I made this launch function in my app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MainViewController *controller = [[MainViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
return YES;
}
And in my MainViewController's viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Title";
self.navigationController.navigationBar.tintColor = [Constants barColor];
....more code...
}
But, in my MainViewController, I'd like to present another view controller called SecondViewController, which needs a UINavigationBar with a back arrow button. So do I make SecondViewController extend UIViewController and do the same thing by setting the title and backButton in the viewDidLoad method? And how do I present it? What should I do to accomplish this?
You'll need to set a root view controller up, it's easiest starting from the apple template.
Here's where the magic happens:
UIViewController *controller = [[UIViewController alloc] initWithNibName:#"MyNib" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
The nav controller does all the work for you (back buttons, titles, animations) - it keeps track!
My workflow is this:
Setup MutableArray in the viewDidLoad, add controllers to it, e.g:
NSMutableArray *array = [[NSMutableArray alloc] init];
MyCustomViewController *customView = [[MyCustomViewController alloc] initWithNibName:#"nib" bundle:#"nil"];
customView.title = #"Second Level";
[array addObject:customView];
self.controllers = array;
Then in your delegate:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
UIViewController *childControllerToBe = [controllers objectAtIndex:row];
[self.navigationController pushViewController:childControllerToBe animated:YES];
}
This, along with a lot more can be learnt by reading a decent beginner book such as Beginning iPhone Development
Also, apple docs are always good :)
UINavigationController is a subclass of UIViewController, but unlike UIViewController it’s not usually meant for you to subclass. This is because navigation controller itself is rarely customized beyond the visuals of the nav bar. An instance of UINavigationController can be created either in code or in an XIB file with relative ease.
Please visit "How to add UINavigationController Programmatically"
You should Push it onto the navigation stack.
This Lecture by Stanford's iPhone Course will teach you a lot about Navigation Bars. (It's a quick read)
Basically at the heart of it you need this code:
[self.navigationController pushViewController:SecondView];
You can use PopViewController to go back programmatically, but the Back Button is automatically created.
Here's some source code from the Lecture. It covers exactly what you are having issues with.

How to call another class on navigation controller and add it to app delegate

I have created a view based application and in the appdelegate .h file I have created UINavigationcontroller object and added it to window subview. Next in the app did finish launching I had allocated and initialised a class that I want to be viewed when the app first launches with the navigation bar on the top of it.
So this is the code I have did to add the class to navigation bar
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the view controller's view to the window and display.
[window addSubview:navController.view];
//test is the class that i want to be viewed first when the app launches first
test *t = [[test alloc]initWithNibName:#"test" bundle:nil];
[navController.view addSubview:t.view];
[window makeKeyAndVisible];
return YES;
}
but the problem is my test class launches but it does not display the navigation bar. What may be the problem?
Use some like this (add these lines)
appDelegate.h
UINavigationController *navController
#property (nonatomic,retain) UINavigationController *navController;
and in .m
test *t = [[test alloc]initWithNibName:#"test" bundle:nil];
self.navController=[[[UINavigationController alloc] initWithRootViewController:t] autorelease];
[window addSubview:self.navController.view];
[window makeKeyAndVisible];
it helps you.
From other view you need to make object of the appDelegate class then access the navigation controller of that class
see this
yourAppDelegateClass *objAppDelegate=(yourAppDelegateClass *)[[UIApplication sharedApplication] delegate];
now for pushing a view
SecondView *s=[[[SecondView alloc] initWithNibName:#"yournibName" bundle:nil] autorelease];
[objAppDelegate.navController pushViewController:s animated:YES];
this concept help you.
You shouldn't add your own view controller's view as a subview of the navigation controller. This way, you hide the navigation controller's view (including the navigation bar) and it makes the navigation controller pointless, because it doesn't know that your view controller wants to be part of its navigation stack.
Instead, push it on the navigation controller's stack by using:
[navController pushViewController:myViewController animated:NO];
Navigation bar is not coming because you are setting navigation controller's view to your test view. You can do it by setting navigation controller's view controllers which requires an array as -
test *t = [[test alloc]initWithNibName:#"test" bundle:nil];
NSArray *viewControllers = [[NSArray alloc] initWithObjects:t,nil];
[self.navigationController setViewControllers:viewControllers];
First, I don't see where you alloc init your navController. Secondly, you don't add a viewController to a navigation controllers view, rather you push the view controller onto the stack, like this:
[navController pushViewController:t animated:NO];
Hope this helps.

Use modal view as "normal" view

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