Custom nav bar styling - iOS [duplicate]
Asked Answered
F

7

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

Fingerbreadth answered 7/4, 2011 at 4:3 Comment(2)
Take a look at this: (Custom UINavigationBar) ios-blog.co.uk/iphone-development-tutorials/… Hope it helpsAstromancy
github.com/piotrbernad/FlatUIPizza
C
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

Carbonari answered 7/4, 2011 at 9:1 Comment(6)
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.Watereddown
@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?Fryer
@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.Watereddown
@ 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?Fryer
@Jenox I'm successfully using some more Sebastian Celis's trickery: 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:.Fideism
@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. :-)Fideism
F
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.

Fryer answered 17/6, 2011 at 18:15 Comment(10)
Correct, but discussion of iOS 5 is still under NDA until it is available publicly. :)Watereddown
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.Carbonari
+1 for being on the cutting edge of unreleased software.Hygrostat
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 ...Breathtaking
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.Breathtaking
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?)Loralyn
@adeel he's doing something like: if ([[UINavigationBar class]respondsToSelector:@selector(appearance)]) { ...do mods... }Waftage
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?Tripterous
@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?Chibouk
@Chibouk I am not using storyboards, so I'm afraid I cannot help you here.Fryer
C
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

Carbonari answered 7/4, 2011 at 9:1 Comment(6)
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.Watereddown
@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?Fryer
@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.Watereddown
@ 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?Fryer
@Jenox I'm successfully using some more Sebastian Celis's trickery: 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:.Fideism
@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. :-)Fideism
A
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.

Allargando answered 19/10, 2011 at 14:59 Comment(5)
Thanks for sharing this. I just released my app to find this "feature" in iOS 5. Quick fix.Effable
Tried this but on retina displays the iOS5 method is drawing litteraly the size of the image, not just with double density.Preiser
This is a great answer, but where do you put the top piece of code?Grocer
@Preiser did you find a solution to your issue? I'm having the same problemBlamed
the second code snippet is crappyRaven
B
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];
}
Borecole answered 25/6, 2012 at 19:47 Comment(1)
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.Felicita
N
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

Nannettenanni answered 10/3, 2012 at 9:11 Comment(0)
U
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];
}
Unpaid answered 9/5, 2012 at 10:56 Comment(5)
This is just iOS 5.0+ specific...Austro
@BorutTomazin yes you are right. for lower versions drawRect can be used.Unpaid
draw rect is also no acceptable cos it apply the image to all navigation bars across the app. Not good!Austro
hmmm! But I can't find any other way to achieve it. I know only these 2 ways. Sorry. :(Unpaid
This is the best solution ever: sebastiancelis.com/2009/12/21/…Austro
C
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.

Camm answered 7/4, 2011 at 4:8 Comment(1)
yes you can set by tint color and the bottom is toolbar , i guessBullfight

© 2022 - 2024 — McMap. All rights reserved.