UIToolbars in iPhone OS 2.x

The new release of iPhone OS 3.0 adds some nice API:s for managing a contextual toolbar. This is well needed as toolbars in the current iteration of iPhone OS is not only poorly documented, it is also quite hard to do right. So I will go over how to do toolbars the right way, for all who want to implement them the old way before this summer, and for all who think they need to support older versions after then (Read my previous post on why backward compatibility is not an issue for iPhone OS developers).

The Problem

The most intuitive way to create and use a UIToolbar is to add it manually for each of your view controllers. If your application is very simple this could be the right thing to do, but if our application uses a UINavigationController then it is the wrong way to do it.

If you look at the Mail application you will notice how the toolbar stays in place, and the items on the toolbar cross fades when you navigate the application hierarchy. You might also have noticed that the toolbar do not cross fade, but rather is pushed, if you add a toolbar to each of the view trees of your own view controllers. This is because your toolbar is then part of the wrong three hierarchy, as shown in this image.

Screen estate as owned by different view controllers.

Screen estate as owned by different view controllers.

You will find a few solutions if you Goole on the topic, all of them that I have found are dead wrong. They tend to use self.view.superview on the view controller instance to slide the toolbar up one level in the hierarchy. It works, most of the time, but is an ugly hack that is not future proof.

A Better Solution

The reason the toolbar is pushed off screen is that the toolbar is owned by the navigation controller,as we saw in the image above, and a navigation controller pushes it’s content. The view hierarchy we would like to have is instead something like this.

Screen estate as owned by different view controllers.

Screen estate as owned by different view controllers.

In this setup the navigation controller can push the content, as it is intended to do, and the custom toolbar controller can manage a static UIToolbar with cross fades. Only problem is; this custom toolbar controller do not exist, and we must implement it on our own. Luckily not many lines of code is needed.

Custom Toolbar Conroller Requirement

The custom toolbar controller should be a subclass of UIViewController, that way it’s fits nicely into the Cocoa Touch framework, and can nicely be fitted into any well written application.

The custom toolbar controller should manage a view consisting of a UIToolbar instance, and a view that is fetched from another view controller. This is modelled after how a UINavigationController manages a view that consists of a UINavigationBar and the view fetched from other view controllers. This way any well written view controller can be used with our custom toolbar controller.

Lastly our custom toolbar controller should implement the UINavigationControllerDelegate protocol, so that we can listen to events as view controllers are pushed and poped from the navigation stack, and update the toolbar with contextual toolbar items. Just as the navigation controller updates it’s title by fetching the title property of the managed view controllers, so will our custom toolbar controller update the toolbar items by fetching the toolbarItems property if available.

Toolbar Controller Interface

The interface will be sparse, all we need is an initializer for creating a toolbar controller with another view controller providing the main content, and for sports two properties to access the content view controller, and the toolbar itself.

Toolbar Controller Implementation

The grunt work for implementing the toolbar controller do not need any further explanation.

The first interesting code is to setup the view that is managed by our toolbar controller. This view have two subviews, a UIToolbar, and a view that we fetch from the content view controller we where given. We do this setup in the loadView method.

This code reuses the screen setup already done by the content view controller, and re-layouts our subviews. It is a simple solution, and you will need a custom class for the root view with an overloaded layoutSubviews if you want to properly handle orientation changes. But I have left this out, as it is not within the scope of this post.

Next up we need to update the toolbar with contextual toolbar items, when our content changes. This is only supported if the content view controller is a UINavigationController, as can be seen in the initializer above. This could be made more flexible, but is also outside the scope of this post. We have set our toolbar controller to be the delegate for the content navigation view controller, so all we need to do is to respond to navigation events as they occur.

Conclusion

As shown with this code, doing the correct way with Cocoa Touch almost always means doing it in the easiest way as well. The hard part is knowing what the correct way is. My hope is that this post can guide you in the right direction. Rule of thumb is to always try to follow the same pattern as similiar things in Cocoa, it might feel like it is harder to do, but at the end of the day you will end up with less code, with more features.

The code above for implementing the custom toolbar controller, is much less code than what is needed to write a small application to demonstrate it being used. I have therefor made a small demonstration application with this complete code. It is a tree of table views, where each item opens up a new table view tree with one less toolbar item.

The source code is released under BSD license, and you can download it here.

26 Comments

  1. Kalle

    Great post! However, the two top images are broken for me :)

  2. Kalle

    They seem to be broken because they are loaded over https….

  3. losLocKos

    hos is it possible to hide the toolbar, when the next view is pushed … an to let reappear the toolbar, when your’e back at the first view?

    • I would not advice you against hiding the toolbar when the user navigates up and down the view tree, I think it is better to let the navigation bar and the toolbar be static.

      But if you really want to, then add code in navigationController:willShowViewController:animated:. Wrap the code in [UIView beginAnimations:context:] and [UIView commitAnimations] to have the content and toolbar animate nicely in and out. Most view properties are animatable this way, so simple set frame, alpha or some other properties you fancy to update the layout.

  4. Darren

    The source link is broke please fix

  5. @Darren: Should work now, yet another problem with https and wordpress :(.

  6. Thanks for this excellent article. I agree with you that many posts found on this topic offer somewhat screwed solutions.

    I’ll use what I’ve read (and tried) here in order to develop a tabbar with a small bar above the tabs similar to what can be seen in the Bloomberg or AOL finance apps.

  7. Robert

    Nice article, thanks.

    The top images are broken because they are linked to https, and if FF doesn’t know the certificate, it won’t show the image.

  8. Patrick

    Great article, thanks.

    Could you give some additional hints on how to handle orientation changes? I’m finding that the navigation controller and toolbar controller rotate fine, but the content view controller doesn’t.

  9. @Patrick: First you must implement shouldAutorotateToInterfaceOrientation: in CWToolbarController. Prefably let it return what the contentViewController responds.

    Then you must setup proper auto resizing masks in loadView of CWToolbarController. All views except the toolbar should flex in size, the toolbar should only flex in with and top margin.

    Then you must create a custom subclass of UIView, that implements layoutSubviews. This is because UINavigationController makes some assumptions, that will screw up the layout, placing it’s content behind the toolbar. Cut and paste the layout that is done in loadView.

    Hope it helps.

  10. Johan De Cock

    Your forgot to add overrides for things like viewWillAppear events in your toolbar controller, which should be something like this:

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

    Otherwise these events won’t get fired in your child viewcontrollers!

  11. Rob

    Any chance you’ve been able to get the rotation actually working correctly ? Seems to give some strange results even after setting all the autoresizing masks on the view and content view..

    • No I have not got rotation to work properly fo all cases, with good performance. I spoke with the Apple engineers about this at WWDC09, and their short answer is that for iPhone OS 2.x it is not easily done without some ugly hacks.

      Move on to iPhone OS 3.0 if screen rotation is a required feature.

  12. Ben

    In Iphone OS 3 it wont compile and gets the error “Error: duplicate member ‘_toolbatitems’”

    Is there a solution to this?

  13. Andy

    What are the new API’s added for contextual UIToolbars in iPhoneOS 3.0? I can’t find much updated for UIToolbar other than the new translucent property. I’m having issues with my toolbar auto-rotating and i’m wondering if it was made easier in iPhoneOS 3.0. Thanks!

    • The new API:s are not added to UIToolbar itself, but to UINavigationController and UIViewController. UIViewController now has a toolbarItems property, that is an array of UIBarButtonItems. The UINavigationController will query this array and use the contents as view controlelrs are pushed and popped to contextually update it’s toolbar.

      That is the UINavigationController now handles a UIToolbar at the bottom of the screen, in addition to the UINavigationBar at the top of the screen.

  14. Jarmo

    Ben, starting in OS 3.0 UINavigationController has an optional toolbar feature and it’s introduced a name conflict. So you could either make use of that native feature instead of Frederik’s method. Or potentially just rename your local toolbarItems member variable to something unused, such as my_toolbarItems.

  15. Wash

    Fredrik,
    I have been looking for an explanation like that for a while. Although I still have not really understood what you did (FYI: I’m very new at cocoa dev), I really appreciate the explanation. Here go a few questions: 1) yes, the duplicate _toolbarItems. any chance you can fix it in the code for us less advanced folks? 2) half of the toolbar is under the clock. how’s that fixed?? 3) as I reach level 7 and click on the table cell. the application traps??? 4) where would one implement the doStuff: that it’s in the toolbar???? finally, 5) if I wanted to write a label in small font right justified on the Nav Bar (in addition to the title) m how would I do it?????

    Thank you again so much and sorry about many questions but you have the one tutorial that I found worth using for my project.

  16. Mario

    awesome post but it doesn’t compile on snow leopard iPhone OS 3.0

  17. Michael

    still doesn’t compile as Mario mentioned.

  18. Chris Leong

    In CWToolbarController’s function loadView I set the CGRect frame to [[UIScreen mainScreen] applicationFrame]. That fixed the sizing problem

  19. nikhil

    Hi it a great blog it almost solve my half of the problem but now i get stucked in mid of my project .I have implemented my project as per your tutorial and declare the doStuff: in the appdelegate.h and define it in appdelegate.m file when it tried to push some view controller in that method it get crashed but there is nil in self.navigationController how can i push a view in doStuff: method

    In appdelegate.m file i have done like this
    -(void)doStuff{
    Events *Event_obj=[[Events alloc]initWithToolbarItems:toolbarItems];
    [self.navigationController pushViewController:Event_obj animated:YES];
    }
    please help me out how can i solve this problem

Trackbacks for this post

  1. Contextual UIToolbars at Under The Bridge
  2. links for 2009-03-29 at adam hoyle presents suckmypixel

Leave a Reply