54

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

Cœur
  • 37,241
  • 25
  • 195
  • 267
phil swenson
  • 8,564
  • 20
  • 74
  • 99

7 Answers7

143

@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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Christian Schnorr
  • 10,768
  • 8
  • 48
  • 83
  • 1
    Correct, but discussion of iOS 5 is still under NDA until it is available publicly. :) – Dave DeLong Jun 17 '11 at 18:21
  • 2
    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. – ludwigschubert Jun 24 '11 at 21:11
  • 23
    +1 for being on the cutting edge of *unreleased* software. – Peter Kazazes Sep 05 '11 at 02:42
  • 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 Oct 09 '11 at 18:07
  • 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. – bryanmac Oct 09 '11 at 18:53
  • 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?) –  Oct 15 '11 at 18:51
  • @adeel he's doing something like: if ([[UINavigationBar class]respondsToSelector:@selector(appearance)]) { ...do mods... } – Maurizio Oct 19 '11 at 20:08
  • 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? – Tek Yin Sep 21 '12 at 10:38
  • @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? – iSeeker Apr 12 '14 at 05:25
  • @iSeeker I am not using storyboards, so I'm afraid I cannot help you here. – Christian Schnorr Apr 13 '14 at 07:21
32

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

Community
  • 1
  • 1
ludwigschubert
  • 662
  • 6
  • 14
  • 13
    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. – Dave DeLong Jun 17 '11 at 18:21
  • @DaveDeLong - It is true that subclassing would be a better idea, but since `UINavigationController`s `navigationBar`is 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? – Christian Schnorr Jun 16 '12 at 09:15
  • @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. – Dave DeLong Jun 16 '12 at 14:17
  • 1
    @ 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? – Christian Schnorr Jun 16 '12 at 15:08
  • 1
    @Jenox I'm successfully using some more Sebastian Celis's trickery: http://sebastiancelis.com/2012/03/05/subclassing-hard-to-reach-classes/ - 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 Jul 17 '12 at 12:44
  • @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. :-) – Ivan Vučica Jul 17 '12 at 12:47
24

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.

bhavinb
  • 3,278
  • 2
  • 28
  • 26
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];
}
chazzwozzer
  • 467
  • 6
  • 14
  • 3
    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. – AriX Nov 15 '12 at 08:57
2

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

Nikita Pestrov
  • 5,876
  • 4
  • 31
  • 66
1

This is a better way for iOS 5

if ([self.navigationController.navigationBar respondsToSelector:@selector(setBackgroundImage:forBarMetrics:)] ) {
 UIImage *image = [UIImage imageNamed:@"navBarImg.png"];
 [self.navigationController.navigationBar setBackgroundImage:image forBarMetrics:UIBarMetricsDefault];
}
iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
iOS Monster
  • 2,099
  • 1
  • 22
  • 49
0

you can change the tint color of navigation bar to change its color and also you can use an image in navigation bar view. For bottom bar i think they are using a view with three custom buttons on it.

saadnib
  • 11,145
  • 2
  • 33
  • 54