Home > iOS Development > Customizing the Appearance of UINavigationBar

Customizing the Appearance of UINavigationBar

Remember how the navigation bar of Apple’s Notes application looks on iPhone? It doesn’t look like one of the system provided styles for navigation bar, neither UIBarStyleDefault nor UIBarStyleBlack. And it also looks nice and elegant, doesn’t it?

It, sometimes, would be nice to be able to change the background of the navigation bar and the color of navigation bar buttons as well. There are several ways for changing the background view of the navigation bar, one is inserting a UIImageView as a subview to the UINavigationBar at index 0. But i found this method to be a bit problematic when it comes to adapt the background view when the device interface orientation changes. Instead i create a UINavigationBar category class and override the default behaviour of the -(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context method. This alters the appearance of all UINavigationBar instances in an application-wide manner and this may be something you don’t want in some cases. However, it is really not so much difficult to use the same technique and change the appearance of UINavigationBars for individual instances. What we’ve done here may also be useful in the case you want to implement a visual theming system for your application. By dynamically changing the images we use as background, it’s possible to make UINavigationBars look different with different themes your application provides.

Let’s have a look at what’s going on in UINavigationBar(CustomBackground) category class:

The two methods +(UIImage *)bgImagePortrait and +(UIImage *)bgImageLandscape provide background images for portrait and landscape interface orientations. File extensions are omitted in image names to tell iOS that HD versions of images are available. Please note that images are lazy-loaded and cached by these methods, and once they are created they will remain in the memory until the application exits. If this is something you don’t want, you can easily modify that part.

Inside the -(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context method, we first determine the interface orientation of the device by checking the width of the current UINavigationBar instance and we choose one of the two images accordingly. Then we draw the image in context. And finally, the -(void)applyCustomTintColor method is what we use in order to change the color of UINavigationBar buttons. This method should be called for every UINavigationController instance we create to modify its UINavigationBar:

#import "UINavigationBar+CustomBackground.h"
...
UINavigationController *navigationController =
        [[UINavigationController alloc] initWithRootViewController:aViewController];
[navigationController.navigationBar applyCustomTintColor];
...

You can download the complete source code of the demo app on github as an Xcode project:
https://github.com/ardalahmet/CustomizingNavigationBarBackground

And here is the Git read-only url, if you’d like to clone it to your local repo:

git://github.com/ardalahmet/CustomizingNavigationBarBackground.git

Some screenshots from the demo app running on the iPhone simulator:

* I should thank to this stackoverflow answer and you may find it useful to have a look at this Q&A on iOS Reference Library.

  1. March 10th, 2011 at 03:32 | #1

    Thanks for this great tutorial and explainer Ardal. I referenced your code and structure to successfully add and size images into an iPad app. I’m stuck in one spot because my app is SplitView which means I have three UINavigationBars to populate. When rotating to landscape, a tableview that appears on the left of the screen has its own UINavigationBar which also gets swapped out. So I’ve been trying unsuccessfully to write a simple three-way if-else statement, based on the following in your code:

    UIImage *image = (self.frame.size.width > 320) ?
    [UINavigationBar bgImageLandscape] : [UINavigationBar bgImagePortrait];

    I’ve not seen this structure before, where you instantiate the image while checking its width, then provide two options separated by a colon :

    Any suggestions how to split this out to test for 704 so that I can implement bgImageLandscape, bgImagePortrait AND bgImageTableheader. I’ll keep searching stackoverflow and apple documentation to see if I can find an explanation of that structure.

    Thanks, Perre

  2. April 2nd, 2011 at 23:37 | #2

    @Perre DiCarlo
    Hi Perre,
    Sorry for the late reply.
    That thing is a language feature and called the “conditional operator”. You can think it as a shorthand for an if-else block. It is exactly identical to something like that:

    UIImage *image = nil;
    if (self.frame.size.width > 320) {
    image = [UINavigationBar bgImageLandscape]
    }
    else {
    image = [UINavigationBar bgImagePortrait];
    }

    You can add one else-if statement to the code above to accomplish your goal. Should look like something like this:

    UIImage *image = nil;
    if (self.frame.size.width == 704) {
    image = [UINavigationBar bgImageTableheader]
    }
    else if (self.frame.size.width == 320) {
    image = [UINavigationBar bgImagePortrait];
    }
    else {
    image = [UINavigationBar bgImageLandscape];
    }

    Hope this helps, good luck..

  3. Tunyk Pavel
    April 11th, 2011 at 15:02 | #3

    Hi. Really god solution. Thx!
    I’m newer in ios programming, can u explain how to change background (using your solution) in non-wide application case? For example, I need to change background in two views. Thank you.

  4. Bjorn
    June 16th, 2011 at 22:55 | #5

    Hi and thanks for the great tutorial,
    problem is that this example works for the iPhone 4.3 simulator but not for 5.0, the background image won’t show, the tint is working though… any ideas how it could be modified to work?

  5. June 17th, 2011 at 14:13 | #6

    @Bjorn
    Hi. I couldn’t have time to check what’s changed in 5.0 in detail. So I don’t have any idea at the moment. Did you try it on the device? If I have the chance to test and solve the problem, I’d let you know.. Thanks for stopping by.

  6. Bjorn
    June 18th, 2011 at 11:52 | #7

    oh, I haven’t installed it on any device yet, thanks for your answer!

  7. August 28th, 2011 at 16:11 | #8

    Hi, in my universal app , I only want customizing the UINavigationBar on iPhone/iPod touch. If use above code, it will change the iPad’s UINavigationBar, could you help me ?

  8. August 29th, 2011 at 03:14 | #9

    @Dada
    Try checking whether or not the device is an iPad and write conditional code to handle the iPad case. If the device is an iPad disable drawLayer:inContext: method definition. Note that I didn’t test this solution, so you should try and see if it works.

  9. Ben
    September 11th, 2011 at 15:15 | #10

    This was much help! Thanks for posting this!

  10. zgia
    November 28th, 2011 at 16:26 | #11

    I downloaded the example from github, but the example cannot show background image in iphone 5 simulator, only show in 4.3 simulator.

    ======

    Developer Information:

    Version: 4.2 (4D199)
    Location: /Developer
    Applications:
    Xcode: 4.2 (828)
    Instruments: 4.2 (4233)
    Dashcode: 3.0.2 (336)
    SDKs:
    Mac OS X:
    10.6: (10K549)
    10.7: (11C63)
    iPhone OS:
    5.0: (9A334)
    iPhone Simulator:
    4.3: (8H7)
    5.0: (9A334)

  11. November 28th, 2011 at 16:55 | #12

    @zgia
    The code has some issues with iOS5. I didn’t have the time to make it compatible with iOS5. Will do it when I have the time.
    Cheers.

  1. No trackbacks yet.