iPhone SDK Articles

Tuesday, September 16, 2008

Navigation Controller + UIToolbar


In this tutorial we will learn how to add a UIToolbar to an app with UINavigationController.
In this tutorial we will learn how to add a UIToolbar to an app with UINavigationController. I had a requirement where I wanted to add a UIToolbar with one button. Clicking the button will load a view controller which is the same view controller when a UITableViewCell is selected and this is what I did.

This is how the final app will look like


Start by creating a new project by selecting "Navigation-Based Application". Open the header file of "RootViewController" and create a variable of type UIToolbar and UINavigationController. This is how the file should look like, without the comments. We also create two variables of the same UIViewController and this will be explained later.

#import <UIKit/UIKit.h>

@class InfoViewController;

@interface RootViewController : UITableViewController {

UIToolbar *toolbar;
InfoViewController *ivControllerToolbar;
InfoViewController *ivControllerCell;
UINavigationController *infoNavController;
}

@end

Open the implementation file and write the following code in viewWillAppear method. This is the method which is always called when the view is going to appear.

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];

//Initialize the toolbar
toolbar = [[UIToolbar alloc] init];
toolbar.barStyle = UIBarStyleDefault;

//Set the toolbar to fit the width of the app.
[toolbar sizeToFit];

//Caclulate the height of the toolbar
CGFloat toolbarHeight = [toolbar frame].size.height;

//Get the bounds of the parent view
CGRect rootViewBounds = self.parentViewController.view.bounds;

//Get the height of the parent view.
CGFloat rootViewHeight = CGRectGetHeight(rootViewBounds);

//Get the width of the parent view,
CGFloat rootViewWidth = CGRectGetWidth(rootViewBounds);

//Create a rectangle for the toolbar
CGRect rectArea = CGRectMake(0, rootViewHeight - toolbarHeight, rootViewWidth, toolbarHeight);

//Reposition and resize the receiver
[toolbar setFrame:rectArea];

//Create a button
UIBarButtonItem *infoButton = [[UIBarButtonItem alloc]
initWithTitle:@"Info" style:UIBarButtonItemStyleBordered target:self action:@selector(info_clicked:)];

[toolbar setItems:[NSArray arrayWithObjects:infoButton,nil]];

//Add the toolbar as a subview to the navigation controller.
[self.navigationController.view addSubview:toolbar];

//Reload the table view
[self.tableView reloadData];

}

Start by initializing the toolbar and then calculate some values. These values determine the position and the width/height of the toolbar. A button is created which will be added to the the toolbar and when the button is clicked "info_cancel" method is called. The button are added from LTR. The toolbar is then added as a subview to the navigation controller.

Add a new view using IB and I have named it "InfoView". Create a UIViewController in XCode and name it InfoViewController and assign this class as the class to be used by the InfoView nib file. Declare a Boolean property in InfoViewController called "isViewPushed" which will be true when a view is pushed to the top of the stack and false when the view is presented as a modal view. Based on the value of this variable we add a cancel button when the view is loaded as a modal view.

This is how the info button will look like when clicked
- (void) info_clicked:(id)sender {

//Initialize the Info View Controller
if(ivControllerToolbar == nil)
ivControllerToolbar = [[InfoViewController alloc] initWithNibName:@"InfoView" bundle:[NSBundle mainBundle]];

ivControllerToolbar.isViewPushed = NO;

//Initialize the navigation controller with the info view controller
if(infoNavController == nil)
infoNavController = [[UINavigationController alloc] initWithRootViewController:ivControllerToolbar];

//Present the navigation controller.
[self.navigationController presentModalViewController:infoNavController animated:YES];

}

From the header file we know that we have declared two types of ViewControllers for the same nib file, this is because for some reason having one variable to control the same view controller results in some unexpected behavior. When the info button is clicked we first initialize the ivControllerToolbar, then we initialize the infoNavController with the ivControllerToolbar view controller. Finally, we present the infoNavController using "presentModalViewController" method of the navigationController. If we do not do this, then the infoview will be loaded without a navigation controller and we will have no graceful way of getting back to the RootViewController. Also, notice that we tell the infoviewcontroller explicitly that the controller is not pushed. This variable will be used in viewDidLoad method of the InfoViewController.

if(isViewPushed == NO) {

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self action:@selector(cancel_Clicked:)] autorelease];
}

Here we check if the view is pushed it is only then we add the cancel button to the left bar button item. When the button is clicked "cancel_Clicked" method is called which will dismiss the modal view presented. This is how the code looks like

-(void) cancel_Clicked:(id)sender {

[self.navigationController dismissModalViewControllerAnimated:YES];
}

Since, this is a table view a user can select a row and go to its detail view. We have some sample and this is how the didSelectRowAtIndexPath method will look like.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic -- create and push a new view controller

//Initialize the info view controller.
if(ivControllerCell == nil)
ivControllerCell = [[InfoViewController alloc] initWithNibName:@"InfoView" bundle:[NSBundle mainBundle]];

ivControllerCell.isViewPushed = YES;

//Push the view controller to the top of the stack.
[self.navigationController pushViewController:ivControllerCell animated:YES];
}

First we initialize the info view controller called "ivControllerCell" which is specifically used to load the same view controller in didSelectRowAtIndexPath method. This time we tell the info view controller that the view will be pushed to the top of the stack.


This is the easiest way I found to add a UIToolbar to an application with UINavigationController. You can download the source code here and please leave me your comments.

Happy Programming
iPhone SDK Articles

23 comments:

Mike said...

When I have the need to put a UITabBar in a UINavigationController I just subclass UINavigationController and in the loadView method add the toolbar to the UINavigationController's view. It keeps the code clean and simple and allows the UINavigationController to ask each UIViewController pushed or popped what toolbarItems is should display.

Franco Solerio said...

Your implementation hides the last cell of the table view under the toolbar when there are many cells.
Try to return 10 in numberOfRowsInSection in your root controller and try to click the last cell.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}

iPhone SDK Articles said...

Hi Franco,

Good catch, I got around it by setting the Inset Bottom value to 45 of the UITableView in Interface Builder.

Thanks,
iPhone SDK Articles

Teresa said...

Do you know how to enable rotation of the toolbar when the orientation changes from portrait to landscape?

Lutz said...

I have a scenario where I want to hide the navigation bar when the Info button is pressed (nd show it again when the button is pressed again.

The code works but hiding the navigation controller does move the toolbar up 44 pixels, and makes the Info button inoperable. I could use animation to push the toolbar back down, but what about the toolbar items? Is there a way to tell the toolbar to refresh the children?

Kaleb said...

Thats Awsome, You have no idea how long I've been trying to find an example like this. What needs to be changed to add a second row?

Anonymous said...

Hi!
How to use the second level with Navigation controller? I mean we have ViewMaster1-ViewDetails1 ---> and go to the ----> ViewMaster2-ViewDetails2. How to realize it?
Thanks a lot!!!

Halloween said...

Thanks a lot for this tutorial, really appreciate it

Dan said...

Hi

Thanks a lot for the article, it was very helpful. However, I am implementing this on a view that is pushed in by an original one. When this view comes in, the toolbar comes in nicely, but when I press back to return to the original view, it stays there.

How would I remove it?

iPhone SDK Articles said...

@Dan you can use toolbar.hidden = YEs to hide the toolbar. Let me know if that works or not.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Lutz You can try using setNeedsDisplay or setNeedsDisplayInRects or set the content mode to UIViewContentModeRedraw to invoce the drawrect method.

I hope this helps

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Kaleb to add a second row, all you need to do is change the number returned in numberOfRowsInSection method.

You can read my SQLLite tutorial series here to learn about UITableView here http://www.iphonesdkarticles.com/search/label/SQLite

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

To get this working ViewMaster1-ViewDetails1 ---> and go to the ----> ViewMaster2-ViewDetails2 you need to create a new nib file withe the table view in it and load that nib file when a row is clicked at level one.

Hope this helps

Happy Programming,
iPhone SDK Articles

Matt said...

Can you go into more detail regarding the Inset Bottom value of 45 so the toolbar isn't covering the last row? I've tried everything and the value doesn't seem to have any effect. The toolbar still covers up the last row. Are there other alternatives so the last visible row is being shown above the toolbar?

Thailand Spa Industry said...

Hi Great article!

I have a follow up question from @Kaleb question as well. I found how to add more rows.. Can you help how I populate these rows?

iPhone SDK Articles said...

@Thailand Spa Industry
tableview:numberOfRowsInSection tells the table view how many rows it should expect. tableView:cellForRowAtIndexPath is called n number of times where n is the number returned in tableView:numberOfRowsInSection.

You can also get the data from a database. You can read the SQLite tutorials here http://www.iphonesdkarticles.com/search/label/SQLite

To learn more about using table views, click here http://www.iphonesdkarticles.com/search/label/UITableView

Happy Programming,
iPhone SDK Articles

Anonymous said...

Thanks for these great articles!

Pierre said...

How can I do to display a scroll view in this view to zoom in/out an image?

iPhone SDK Articles said...

@Pierre I don't this is a good control to add a image. You may want to add a image to a UIImageView placed on the view.

I hope this helps.

Happy Programming,
iPhone SDK Articles

Anonymous said...

Hi. I'm using the toolbar hidden property to hide it once a button is clicked. Then when returning to the RootController, I set hidden to NO. That works but the toolbar seems to abruptly appear after the RootController view displays. It's distracting. Is there someway to animate or slide it in?

iPhone SDK Articles said...

In which method are you setting the hidden property to NO, can you try setting it in viewWillAppear method and see how it works?

Please let me know how it goes and also feel free to send me an email at iphonearticles at gmail.com

Happy Programming,
iPhone SDK Articles

Anonymous said...

This is great. Helped me a lot. Thx.

Fredrik Olsson said...

This tutorial is not future proof, and requires a few hacks to works. A more proper and Cocoa Touch elegant solution is outlined here:
http://blog.jayway.com/2009/03/22/uitoolbars-in-iphone-os-2x/