Custom nav bar styling - iOS
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.
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.
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
phil swenson
Updated on July 09, 2022Comments
-
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?
-
Kshitiz Ghimire about 13 yearsyes you can set by tint color and the bottom is toolbar , i guess
-
Dave DeLong almost 13 yearsCorrect, but discussion of iOS 5 is still under NDA until it is available publicly. :)
-
Dave DeLong almost 13 yearsSwizzling
-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 almost 13 yearsI 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 over 12 years+1 for being on the cutting edge of unreleased software.
-
bryanmac over 12 yearsI 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 over 12 yearsfigured 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 over 12 yearsbryanmac: 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 over 12 years@adeel he's doing something like: if ([[UINavigationBar class]respondsToSelector:@selector(appearance)]) { ...do mods... }
-
DenVog over 12 yearsThanks for sharing this. I just released my app to find this "feature" in iOS 5. Quick fix.
-
sebrock over 12 yearsTried this but on retina displays the iOS5 method is drawing litteraly the size of the image, not just with double density.
-
MusiGenesis about 12 yearsThis is a great answer, but where do you put the top piece of code?
-
clopez about 12 years@sebrock did you find a solution to your issue? I'm having the same problem
-
Borut Tomazin about 12 yearsThis is just iOS 5.0+ specific...
-
iOS Monster about 12 years@BorutTomazin yes you are right. for lower versions drawRect can be used.
-
Borut Tomazin about 12 yearsdraw rect is also no acceptable cos it apply the image to all navigation bars across the app. Not good!
-
iOS Monster about 12 yearshmmm! But I can't find any other way to achieve it. I know only these 2 ways. Sorry. :(
-
Borut Tomazin about 12 yearsThis is the best solution ever: sebastiancelis.com/2009/12/21/…
-
Christian Schnorr almost 12 years@DaveDeLong - It is true that subclassing would be a better idea, but since
UINavigationController
snavigationBar
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? -
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 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 almost 12 yearsthe second code snippet is crappy
-
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-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 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. ReplacingdrawRect:
worked prior to iOS 4, and the "proper" way usingUIAppearance
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 usingUIAppearance
is the only future-proof way, anyway. :-) -
Tek Yin over 11 yearsindex 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 over 11 yearsI'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 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 about 10 years@iSeeker I am not using storyboards, so I'm afraid I cannot help you here.