2

I have a problem to change the background image of a UINavigationBar for IOS version < 5. I read already about one good solution, which is based on method swizzling, but the problem of this solution is when I add the image it covers everything include the buttons on a navigation bar. I found a solution which partially worked for me it is base on a following code:

@interface UINavigationBar (UINavigationBarCategory)
-(void)setBackgroundImage:(UIImage*)image withTag:(NSInteger)bgTag;
-(void)resetBackground:(NSInteger)bgTag; 
@end

@implementation UINavigationBar (UINavigationBarCategory)

-(void)setBackgroundImage:(UIImage*)image withTag:(NSInteger)bgTag{
if(image == NULL){ //might be called with NULL argument
    return;
}
UIImageView *aTabBarBackground = [[UIImageView alloc]initWithImage:image];
aTabBarBackground.frame = CGRectMake(0,0,self.frame.size.width,self.frame.size.height);
aTabBarBackground.tag = bgTag;
[self addSubview:aTabBarBackground];
//[self sendSubviewToBack:aTabBarBackground];
[aTabBarBackground release];
}
-(void)setRightButton:(UIButton*)button withTag:(NSInteger)bgTag{
if(button == NULL){ //might be called with NULL argument
    return;
}
    [self addSubview:button];
}

/* input: The tag you chose to identify the view */
-(void)resetBackground:(NSInteger)bgTag {
[self sendSubviewToBack:[self viewWithTag:bgTag]];
}
@end

I used this Category in my ViewWillAppear methods like this:

-(void) viewWillAppear:(BOOL)animated {
 UIImage *backgroundImage = [UIImage imageNamed:@"background_confernce_import_logo"];

if ([self.navigationController.navigationBar  respondsToSelector:@selector(setBackgroundImage:forBarMetrics:)])
{
    [self.navigationController.navigationBar setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault];
}
else{

   [[self.navigationController navigationBar] setBackgroundImage:backgroundImage     withTag:8675309];
}
}

In else clause I call setBackgroundImage. It is ok, but the problem is that if I have a right button on navigation bar of page 1 for example and go to page 2 after come back to page 1 the button is disappear. I should change the background image of navigation bar in every page in my application like this in viewWillAppear method where I put the new image. Any help will be appreciated. Under IOS 5 there are no such problem, but it should work on both versions.

Dharmesh Dhorajiya
  • 3,976
  • 9
  • 30
  • 39
Petko Yanakiev
  • 159
  • 2
  • 8
  • possible duplicate of [Custom UINavigationBar Background](http://stackoverflow.com/questions/704558/custom-uinavigationbar-background) – Till Jan 19 '12 at 21:00
  • @Till, the answer may be pretty much the same (as the link you post), but the question is slightly different, since it asks about a concrete attempt at implementing the custom background... – sergio Jan 19 '12 at 21:07
  • @sergio Fair enough - makes sense. – Till Jan 19 '12 at 21:09

2 Answers2

1

I hate to say it, but your approach (adding a subview to hold the background) will not work exactly for the reason you mention. Each time the navigation bar is redrawn, the subview will not keep its z-order (and thus it will cover other UI elements). This behavior is described by other sources (see this, e.g.)

If you don't want to use swizzling, you could override drawRect in a category, so that the background is always drawn correctly. (this last option has the drawback that any navigation bar in your app will be drawn with the same background). This is a sample code I use:

@implementation UINavigationBar (CustomBackground)

 - (void)drawRect:(CGRect)rect { 
    UIImage *image = [UIImage imageNamed: @"back.png"]; 
    [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
 }
@end

A different approach could be:

  1. subclassing UINavigationBar;
  2. overriding drawRect;
  3. in Interface Builder, set the class of your navigation bar object to your UINavigationBar subclass.

I haven't tried it, but it should work.

sergio
  • 68,819
  • 11
  • 102
  • 123
  • 1
    Method swizzling has become a NoNo - Apple will reject your app if you do it. – Till Jan 19 '12 at 21:07
  • This solution set only one image for every nav bar I need a different image for every page. Exactly as you mention in your last line. I saw Sebastian Celis code for method swizzling but it paint all include the buttons I want the buttons in the navbar to stay intact. – Petko Yanakiev Jan 19 '12 at 21:10
  • @PetcoYanakiev you could however customize this method even further to use any image. One quick and dirty solution could be a global NSString that contains the "currently" desired image name. Before showing a new view controller (e.g. by pushing it on the navigation stack), you could update this image name with the desired one. – Till Jan 19 '12 at 21:14
  • @Till Show an example for this. At the moment I put an image in the setBackgroundImage in viewWillAppear. – Petko Yanakiev Jan 19 '12 at 21:27
0

As per request, here comes my slightly naughty and not really polished hack. This is really just for making the OP happy. The right answer was given by sergio.


UINavigationBar+CustomDraw.m

NSString *gNavbarBackgroundImageName = @"default_navbar_background.png";

@implementation UINavigationBar (CustomBackground)

- (void)drawRect:(CGRect)rect 
{ 
    if (gNavbarBackgroundImageName != nil)
    {
        UIImage *image = [UIImage imageNamed:gNavbarBackgroundImageName]; 
        [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
    }
}
@end

UINavigationBar+CustomDraw.h

extern NSString *gNavbarBackgroundImageName;

And here comes the example usage in two view controllers...


FooViewController.m

#import "UINavigationBar+CustomDraw.h"

@implementation FooViewController

- (void)viewWillAppear:(BOOL)animated
{
    gNavbarBackgroundImageName = @"foo_navbar_background.png";
    [self.navigationController.navigationBar setNeedsDisplay];
}

@end

BarViewController.m

#import "UINavigationBar+CustomDraw.h"

@implementation BarViewController

- (void)viewWillAppear:(BOOL)animated
{
    gNavbarBackgroundImageName = @"bar_navbar_background.png";
    [self.navigationController.navigationBar setNeedsDisplay];
}

@end

Now let us assume you want to show a non-styled navigation bar, for example when displaying a Facebook login page (as provided by their SDK).

Use this to prevent any custom drawing:

gNavbarBackgroundImageName = nil;

Note Some of Apple's components use the UINavigationBar at places you might not have thought of. For example, the MPMoviePlayerController uses a custom navigation bar for displaying the upper part of its UI - so that would be another case where you want to prevent custom drawing.

Till
  • 27,559
  • 13
  • 88
  • 122
  • I managed to solve the problem with my code I just add method for add right button on navbar and it works now see my edited code. Thanks for your answer. Like this if I need any custom element on my navbar I just aded like subview and voila.I will try your solution because I think is more elegant than mine :) – Petko Yanakiev Jan 19 '12 at 22:37
  • Ow, something I entirely forgot to tell you - make sure you also add something to disable the drawRect "hack" as this might severely hurt you once something is shown that should not be customized. Will add that to my solution right away... – Till Jan 19 '12 at 22:43