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
@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];
}
[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];
}
//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];
}
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];
}
[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];
}
// 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:
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.
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;
}
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
Do you know how to enable rotation of the toolbar when the orientation changes from portrait to landscape?
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?
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?
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!!!
Thanks a lot for this tutorial, really appreciate it
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?
@Dan you can use toolbar.hidden = YEs to hide the toolbar. Let me know if that works or not.
Happy Programming,
iPhone SDK Articles
@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
@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
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
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?
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?
@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
Thanks for these great articles!
How can I do to display a scroll view in this view to zoom in/out an image?
@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
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?
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
This is great. Helped me a lot. Thx.
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/
Post a Comment