I've overrided(placed in category, or swizzled) UINavigationBar's drawRect to show custom background. In iOS 5 it's not working. What should I do?
Setting custom background for UINavigationBar to support iOS5 and iOS4 too!
http://www.mladjanantic.com/setting-custom-background-for-uinavigationbar-what-will-work-on-ios5-and-ios4-too/
http://rogchap.com/2011/06/21/custom-navigation-bar-background-and-custom-buttons/
As you know, until iOS 5 came out, we used drawRect override in AppDelegate to customize UINavigationBar.
But know, iOS 5 give us some new method for styling (and old doesn’t work).
How to build app that will work on iOS 4 and iOS 5 with stylized UINavigationBar?
You must to do both!
In AppDelegate use this code:
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
UIImage *img = [UIImage imageNamed:#"navbar.png"];
[img drawInRect:rect];
}
#end
and in viewDidLoad method for iOS5 (in your view implementation):
if ([self.navigationController.navigationBar respondsToSelector:#selector( setBackgroundImage:forBarMetrics:)]){
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"navbar.png"] forBarMetrics:UIBarMetricsDefault];
}
If you see, here we are asking if navbar will respondToSelector to avoid crash on iOS4!
Here's a less-ugly solution that works for both iOS4 and 5:
#implementation UINavigationBar (CustomBackground)
- (UIImage *)barBackground
{
return [UIImage imageNamed:#"top-navigation-bar.png"];
}
- (void)didMoveToSuperview
{
//iOS5 only
if ([self respondsToSelector:#selector(setBackgroundImage:forBarMetrics:)])
{
[self setBackgroundImage:[self barBackground] forBarMetrics:UIBarMetricsDefault];
}
}
//this doesn't work on iOS5 but is needed for iOS4 and earlier
- (void)drawRect:(CGRect)rect
{
//draw image
[[self barBackground] drawInRect:rect];
}
#end
Try to read iOS 5.0 Release Notes
In iOS 5, the UINavigationBar, UIToolbar, and UITabBar implementations have changed so that the drawRect: method is not called unless it is implemented in a subclass. Apps that have re-implemented drawRect: in a category on any of these classes will find that the drawRect: method isn't called. UIKit does link-checking to keep the method from being called in apps linked before iOS 5 but does not support this design on iOS 5 or later.
There's some possible solutions:
Quickest fix For laziest of us :
#interface MyNavigationBar : UINavigationBar
#end
#implementation MyNavigationBar
- (void)drawRect:(CGRect)rect {
}
#end
#implementation UINavigationBar (BecauseIMLazyHacks)
/*
Another Ugly hack for iOS 5.0 support
*/
+ (Class)class {
return NSClassFromString(#"MyNavigationBar");
}
-(void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.frame.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGRectMake(0, 0,
self.frame.size.width, self.frame.size.height), barBackground.CGImage);
}
#end
Again. It works, but You shouldn't do it like this.
Another way, as suggested in WWDC'11 is to override UINavigationBar (Create MyNavigationBar) and initialize UINavigationController from xib like here :
http://www.iosdevnotes.com/2011/09/custom-uinavigationbars-techniques/
And finally, use logic flow switch for iOS5.0 and iOS5.0-
Use new API where it's possible.
Categories is wrong path, Swizzling is wrong path. (They're just whispering in your ears:"Give yourself to the Dark Side. It is the only way you can save your apps.")
#implementation UINavigationBar (MyCustomNavBar)
- (void)setBackgroudImage:(UIImage*)image
{
CGSize imageSize = [image size];
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, imageSize.height);
UIImageView *backgroundImage = [[UIImageView alloc] initWithImage:image];
backgroundImage.frame = self.bounds;
[self addSubview:backgroundImage];
[backgroundImage release];
}
#end
The above swizzling will allow you to set any custom background image for the UINavigationBar(iOS5 & iOS4).
Follow this link to make your code compatible with iOS4, 5 and 6.
You just have to make in Photoshop or other software a rectangular with the size of 320x44 or 640x88 (for retina display) and import it to your project
In AppDelegate use this code (in the header between #import and #implementation AppDelegate):
#implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed:#"top_bar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
In viewDidLoad use this code for iOS5 and iOS6:
#if defined(__IPHONE_5_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_5_0
if ([self.navigationController.navigationBar respondsToSelector:#selector( setBackgroundImage:forBarMetrics:)]){
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"top_bar.png"] forBarMetrics:UIBarMetricsDefault];
}
#endif
After iOS 5 - (void)drawRect:(CGRect)rect is not called while we create category for UINavigationBar but you can call -(void)awakeFromNib and add all the code that you want to add.
Related
This code works fine with iPad Simulator 4.2, but not with later version of iOS4.3 or after that.I am not able to Override the UIToolbar class methods.
#implementation UIToolbar (CustomImage)
- (void)drawRect:(CGRect)rect
{
UIImage *image = [[UIImage imageNamed:#"ToolBar.png"] retain];
[image drawInRect:rect];
[image release];
}
//return 'best' size to fit given size. does not actually resize view. Default is return existing view size
- (CGSize)sizeThatFits:(CGSize)size {
CGSize result = [super sizeThatFits:size];
result.height = 80;
return result;
};
What would be the alternate solution for this ?Please guide me.
In later version ..- (void)drawRect:(CGRect)rect is never called .
Running with iPad Simulator 4.2 code works fine but with iPad Simulator 4.3 drawRect in never called.
Below is the Screenshot of Toolbar:
What about something like this?
#implementation UIToolbar (UIToolbarCategory)
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if([self isMemberOfClass: [UIToolbar class]]){
[super drawRect:rect];
UIImage *image = [UIImage imageNamed:#"bar_gradient.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
}
#end
you are implementing it as a category, you need to subclass UIToolBar based on the iOS5 change log
In the iOS 5 beta, the UINavigationBar,
UIToolbar, and UITabBar implementations have changed so that the
drawRect: method is not called on instances of these classes unless it
is implemented in a subclass.
Apps that have re-implemented drawRect: in a category on any of these
classes will find that the drawRect: method isn’t called.
UIKit does link-checking to keep the method from being called in apps
linked before iOS 5 but does not support this design on iOS 5 or
later. Apps can either:
Use the customization API for bars that in iOS 5 and later, which is the preferred way.
Subclass UINavigationBar (or the other bar classes) and override drawRect: in the subclass.
I have a simple app with a nav bar controller and I would like to change its navigation bar while overriding the drawRect function. I read in many places here that all I need to do is just to paste the code below above my appDelegate. Sadly, it doesnt seem to do anything for me. I tried as a start to just have the color of the nav bar changed, before i try to add an image to it, but again, nothing happens. What am i missing here
I tired to insert this code above the AppDelegate:
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
UIColor *color = [UIColor redColor];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColor(context, CGColorGetComponents( [color CGColor]));
CGContextFillRect(context, rect);
}
#end
And with the background image:
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
// Drawing code
UIImage *img = [UIImage imageNamed: #"13.png"];
[img drawInRect:CGRectMake(0, 0, 320, self.frame.size.height)];
}
#end
Thanks for the help!
This code declares a category on UINavigationBar and redefines the behaviour of its drawRect: method. Try to put the code snippet (second one) at the end of the .m file of the app delegate.
Although the first should work, another more clean option is to write the category in a separate file you will include in the app delegate.
Also make sure the picture you are loading exists in the app bundle.
Ok - solution found! This doesnt seem to work on the iPhone 5.0 simulator!
I successfully implemented a navigation bar with custom background following the answer posted at Custom background for UINavigationBar problems.
However, I would like to have the standard navigation bar for some of my controllers and I have no clue how I can achieve this.
If I start a new project based on the Navigation-based Application template and just add the UINavigationBar category in separate .h and .m files, this category is applied immediately. No includes or whatever are necessary. How does this work?
Thanks for your help!
Here's a quick hack - use the tag property of your navigation bar to turn on the custom code i.e.
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
if (tag < 500) {
// Drawing code
UIImage *img = [UIImage imageNamed: #"navbar_background.png"];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 320, self.frame.size.height), img.CGImage);
} else {
// Do the default drawing
}
}
#end
Now, navigation controllers with a tag less than 500 use your custom background. If you set the tag to be > 500, you get the default behaviour.
EDIT
As #MikeWeller correctly pointed out, we don't have access to the initial implementation of drawRect, our category has overridden it.
Take a look at this link for a solution - basically, it's a macro that you can include that gives you an extra method :
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
if (tag < 500) {
// Drawing code
UIImage *img = [UIImage imageNamed: #"navbar_background.png"];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 320, self.frame.size.height), img.CGImage);
} else {
// Do the default drawing
invokeSupersequent(rect);
}
}
#end
NB I haven't tried this myself but have used other articles from this blog before with great success so I trust it :) Let us know how you get on!
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
if (tag < 500) {
// Drawing code
UIImage *img = [UIImage imageNamed: #"navbar_background.png"];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 320, self.frame.size.height), img.CGImage);
} else {
// Do the default drawing
}
}
#end
Remeber this method will not work in iOS 5.0 because apple has changed the implementation of UINavigationBar so instead create a subclass of UINavigationBar and override the method there instead of creating category.
Basically I want a custom UINavigationBar. I don't want it to be "translucent" or anything, like the pictures app.
I basically want to completely remove it, but I still want to be able to add back buttons and such when navigation controllers are pushed, and I want the views (EG: UITableViewController) to be pushed down below it.
Like this:
Any ideas how to achieve this at all?
Thanks
#implementation UINavigationBar (background)
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed:#"navigationbar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
basically, its not completely see through - its a visual lie. The only way to do it realistically is to override UINavigationBar's drawRect: method, as shown above.
To see through the UINavigationBar, if you choose to have one, just:
self.navigationController.navigationBar.translucent=YES;
You'll have to change the tint/color to match the background if you want it to appear like the image you posted.
At the beginning of your AppDelegate subclass UINavigationBar as below:
#interface CustomNavBar : UINavigationBar
#end
#implementation CustomNavBar
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed:#"bar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
and then in the AppDelegate do this magic:
//Set custom NavigationBar
[self.navController setValue:[[CustomNavBar alloc]init] forKeyPath:#"navigationBar"];
//Set tint to match bar.png
self.navController.navigationBar.tintColor = [UIColor colorWithRed:0.93 green:0.43 blue:0 alpha:1];
//Set font for NavigationBar
[self.navController.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont fontWithName:#"Comfortaa-Bold" size:20], UITextAttributeFont, nil]];
That should give you a lot more control over UINavigationController look & feel.
Hard to tell, could be the UINavigationBar is there and color matches the UIView background or, there is no UINavigationBar, just a view with custom buttons and UILabel on top. Pick an approach and code it, or ask the question again with more specifics.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to add background image on iphone Navigation bar?
I am looking for a way to have a custom navigation bar and need to have a custom navigation bar background to achieve this. I was looking around for how to do this, but could not find a solution. If anyone has the solution, help is much appreciated.
Since iOS5, you can easily set a custom background, with the method setBackgroundImage:forBarMetrics:
But you must check if the user's phone has the right OS.
if ([self.navigationController.navigationBar respondsToSelector:#selector(setBackgroundImage:forBarMetrics:)])
{
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"texture.png"]
forBarMetrics:UIBarMetricsDefault];
}
This is a nicer solution, cause it's in the doc.
(supplemental to Andrew Johnson's response)
The linked Apple.com post includes 3 or 4 different solutions, most of which only "half" work. I think the most elegant/effective of them is this one:
#implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed: #"NavigationBar.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
HOWEVER ... it's not good-practice ObjC to od that as a category (should be an override), and it has some problems of its own.
So, a more general and powerful solution is here:
http://samsoff.es/posts/customize-uikit-with-method-swizzling
You can just add a subview (a UIImageView) to the navaigationBar, which is just a UIView subclass.
UINavigationBar nb = [[UINavigationBar alloc]init];
[nb addSubview: foo];
[nb release];
Here's a forum post that describes how to wrap this up into a category: http://discussions.apple.com/thread.jspa?threadID=1649012&tstart=0
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];
}