Custom nav bar styling - iOS

79,726

Solution 1

EDIT: This is outdated; for iOS5 there's a much better answer below, by @Jenox.

Completely custom styling for Navigation Bars is surprisingly difficult. The best writeup I know of is this one by Sebastian Celis: http://sebastiancelis.com/2009/12/21/adding-background-image-uinavigationbar/

This doesn't override drawRect, and includes a good explanation why that's a good thing. Also note you don't have to follow his tutorial. You can download the complete code here: https://github.com/scelis/ExampleNavBarBackground

Solution 2

@ludwigschubert's solution does however not work on iOS 5. Neither does overriding -drawRect:, because it isn't even called.
As of iOS 5, a navigation bar consist of a UINavigationBarBackground and a UINavigationItemView. There are two other ways of getting it work.


  1. Insert your custom image view at index 1 instead of 0. This makes it appear above the native background image while staying below the buttons.

  2. Make use of iOS 5's UIAppearance protocol. You can either set the background image for all

[[UINavigationBar appearance] setBackgroundImage:myImage forBarMetrics:UIBarMetricsDefault]

or for just one navigation bar:

[navigationController.navigationBar setBackgroundImage:myImage forBarMetrics:UIBarMetricsDefault]

Make sure to provide two images (UIBarMetricsDefault & UIBarMetricsLandscapePhone) when developing an iPhone app for both portrait and landscape.

Solution 3

Just to add to the answer given by @Jenox, if you want to support both iOS 4.xx and iOS 5.xx devices (i.e. your DeploymentTarget is 4.xx), you must be careful in wrapping the call to the appearance proxy by checking at runtime if the 'appearance' selector is present or not.

You can do so by:

//Customize the look of the UINavBar for iOS5 devices
if ([[UINavigationBar class]respondsToSelector:@selector(appearance)]) {
    [[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"NavigationBar.png"] forBarMetrics:UIBarMetricsDefault];
}

You should also leave the iOS 4.xx workaround that you may have implemented. If you have implemented the 'drawRect' workaround for iOS 4.xx devices, as mentioned by @ludwigschubert, you should leave that in:

@implementation UINavigationBar (BackgroundImage)
//This overridden implementation will patch up the NavBar with a custom Image instead of the title
- (void)drawRect:(CGRect)rect {
     UIImage *image = [UIImage imageNamed: @"NavigationBar.png"];
     [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
@end

This will get the NavBar look the same in both iOS 4 and iOS 5 devices.

Solution 4

Copy this into viewDidLoad. It will check for iOS 5 and use the preferred method, otherwise it will add a subview to the navBar for iOS versions < 5.0. This will work provided that your custom background image has no transparencies.

float version = [[[UIDevice currentDevice] systemVersion] floatValue];
NSLog(@"%f",version);
UIImage *backgroundImage = [UIImage imageNamed:@"myBackgroundImage.png"];
if (version >= 5.0) {
    [self.navigationController.navigationBar setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault];
}
else
{
    [self.navigationController.navigationBar insertSubview:[[[UIImageView alloc] initWithImage:backgroundImage] autorelease] atIndex:1];
}

Solution 5

You can just create a category and create a custom method to add any view you want - images,buttons,sliders. Foe example, here is the code that i use - it adds custom backgroundimage,backButton and Label.

@interface UINavigationBar (NavigationBar)
-(void)setBarForCard;
@end


@implementation UINavigationBar (NavigationBar)

-(void)setBarForCard
{

    UIImageView *aTabBarBackground = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"BarImage"]];
    aTabBarBackground.frame = CGRectMake(0,0,self.frame.size.width,44);
    [self addSubview:aTabBarBackground];
    [self sendSubviewToBack:aTabBarBackground];
    [aTabBarBackground release];

    UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    backBtn.frame =CGRectMake(10, 8, 60, 30);
    [backBtn addTarget:self action:@selector(back:)forControlEvents:UIControlEventTouchUpInside];
    [backBtn setImage:[UIImage imageNamed: @"Back"] forState:UIControlStateNormal];
    [self addSubview:backBtn];

    UILabel *calendar = [[UILabel alloc]init];
    calendar.frame = CGRectMake(105, 13, 109, 21);
    calendar.text = @"Calendar"
    calendar.textColor = [UIColor whiteColor];
    calendar.textAlignment = UITextAlignmentCenter;
    calendar.shadowColor = [UIColor grayColor];
    calendar.shadowOffset = CGSizeMake(0, -1);
    calendar.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:20];
    calendar.backgroundColor = [UIColor clearColor];    
    [self addSubview:calendar];


}

And then, in any view controller, you can change your navigationbar by calling [self.navigationController.navigationBar setBarForCard];

This works both in IOS 4 and IOS 5

Share:
79,726
phil swenson
Author by

phil swenson

Updated on July 09, 2022

Comments

  • phil swenson
    phil swenson almost 2 years

    Possible Duplicate:
    How to add background image on iphone Navigation bar ?

    iOS - How did the NY Times do this custom top navigation bar styling?

    And for that matter, the bottom one?

    ny-times

  • Kshitiz Ghimire
    Kshitiz Ghimire about 13 years
    yes you can set by tint color and the bottom is toolbar , i guess
  • Dave DeLong
    Dave DeLong almost 13 years
    Correct, but discussion of iOS 5 is still under NDA until it is available publicly. :)
  • Dave DeLong
    Dave DeLong almost 13 years
    Swizzling -drawRect: is an idiotically stupid idea. Don't do it; you're screwing with code you don't own, and your code will break in the future. Use a subclass instead. THIS IS WHY SUBCLASSING EXISTS.
  • ludwigschubert
    ludwigschubert almost 13 years
    I should point out that it's not mine, and also that this sounds so much cleaner. I just heard about this new API from you and I will update my answer to point to yours, @Jenox.
  • Peter Kazazes
    Peter Kazazes over 12 years
    +1 for being on the cutting edge of unreleased software.
  • bryanmac
    bryanmac over 12 years
    I have the old way working on iOS 4 and I could switch to UIAppearance but I don't want to require iOS5. I don't mind having both patterns in my code but does anyone have any guidance on how to pull that off ...
  • bryanmac
    bryanmac over 12 years
    figured it out - I simply leave the old way discussed above and surround setting the background with if nav respondsToSelector. Seems to work on iOS4 & 5 devices.
  • Admin
    Admin over 12 years
    bryanmac: could you explain what you mean? I'm trying to do the same thing. What exactly did you surround it with, and what selector did you check for? (You're using ludwigschubert's answer for iOS 4, right?)
  • Maurizio
    Maurizio over 12 years
    @adeel he's doing something like: if ([[UINavigationBar class]respondsToSelector:@selector(appearance)]) { ...do mods... }
  • DenVog
    DenVog over 12 years
    Thanks for sharing this. I just released my app to find this "feature" in iOS 5. Quick fix.
  • sebrock
    sebrock over 12 years
    Tried this but on retina displays the iOS5 method is drawing litteraly the size of the image, not just with double density.
  • MusiGenesis
    MusiGenesis about 12 years
    This is a great answer, but where do you put the top piece of code?
  • clopez
    clopez about 12 years
    @sebrock did you find a solution to your issue? I'm having the same problem
  • Borut Tomazin
    Borut Tomazin about 12 years
    This is just iOS 5.0+ specific...
  • iOS Monster
    iOS Monster about 12 years
    @BorutTomazin yes you are right. for lower versions drawRect can be used.
  • Borut Tomazin
    Borut Tomazin about 12 years
    draw rect is also no acceptable cos it apply the image to all navigation bars across the app. Not good!
  • iOS Monster
    iOS Monster about 12 years
    hmmm! But I can't find any other way to achieve it. I know only these 2 ways. Sorry. :(
  • Borut Tomazin
    Borut Tomazin about 12 years
    This is the best solution ever: sebastiancelis.com/2009/12/21/…
  • Christian Schnorr
    Christian Schnorr almost 12 years
    @DaveDeLong - It is true that subclassing would be a better idea, but since UINavigationControllers navigationBaris readonly you could not display your subclass in a navigation controller. After all, why should swizzling -drawRect: break, when you do 'call super' as a subclass would?
  • Dave DeLong
    Dave DeLong almost 12 years
    @Jenox you've always been able to change the class of the navigation bar. Just select the bar in the xib, hit cmd-opt-3, and type in a different class name for it.
  • Christian Schnorr
    Christian Schnorr almost 12 years
    @ DaveDeLong - Right, it does work with xibs. I do, however, think that Apple did something wrong with IB and went even further in the wrong direction with storyboards. Is it possible to change the navigation bar when I create my controller in code?
  • Vivienne Fosh
    Vivienne Fosh almost 12 years
    the second code snippet is crappy
  • Ivan Vučica
    Ivan Vučica almost 12 years
    @Jenox I'm successfully using some more Sebastian Celis's trickery: sebastiancelis.com/2012/03/05/subclassing-hard-to-reach-clas‌​ses - consisting of using keyed archiver to archive, and keyed unarchiver to unarchive. You replace UINavigationBar with your subclass while decoding using -[NSKeyedUnarchiver setClass:forClassName:.
  • Ivan Vučica
    Ivan Vučica almost 12 years
    @DaveDeLong iOS 5 released a few months after your post has proven even subclassing and replacing drawRect: was idiotically stupid. It is idiotically stupid -- but so is any undocumented trickery. Replacing drawRect: worked prior to iOS 4, and the "proper" way using UIAppearance didn't. So replacing -drawRect: in any way may have been idiotically stupid by not being future-proof, but it surely is past-proof. And using UIAppearance is the only future-proof way, anyway. :-)
  • Tek Yin
    Tek Yin over 11 years
    index 1 / 0 will go above native background and stay below buttons... but if you push a View, UINavigationItemView will go on index 0, under the custom background... which is not visible.. Any solution?
  • AriX
    AriX over 11 years
    I'm pretty sure getting the systemVersion as a float value is a bad idea... It would work fine when the version is 5.0, but what about when the version is 5.0.1? As far as NSString is concerned, that's not a number.
  • iSeeker
    iSeeker about 10 years
    @Jenox Can this be done with only one image for portrait and landscape in a project using storyboard, i.e images are added in there?
  • Christian Schnorr
    Christian Schnorr about 10 years
    @iSeeker I am not using storyboards, so I'm afraid I cannot help you here.