UINavigationBar and method swizzling

April 12, 2011

You don’t have to use many iOS apps before you realize that several deviate from the standard gray/blue look and feel. Facebook, yelp, and zipcar all use different colors on the UINavigationBar to provide a unique look. When writing my own apps I wanted to do something similar. You can set the tintColor of the navigation bar or customize the UI further by using an image as the background.

Check out UIAppearance for a more modern solution. There methods described here are for iOS 4 and earlier.

I found several links discussing about method swizzling as a way to change the background color on a UINavigationBar. Not only did this sound like a really cool objective-c feature to learn about but it was also easy to implement. I used code similar to Devin and Sam’s implementations

First create a category class called UINavigationBar+CustomBackground.m or something similar. The plus sign in the middle of the file name is a convention followed by Apple.

#import "UINavigationBar+CustomBackground.h"

@implementation UINavigationBar (CustomBackground)

- (void)drawRectCustomBackground:(CGRect)rect {
    if (self.barStyle == UIBarStyleDefault) {
        UIImage *image = [UIImage imageNamed:@"title-bar.png"];
        CGRect fillRect = CGRectMake(0.0, 0.0, 
                                     self.frame.size.width,
                                     self.frame.size.height);
        CGContextDrawImage(UIGraphicsGetCurrentContext(), fillRect, [image CGImage]);
        return;
    }
    
    // we swizzled drawRect: method with this one
    [self drawRectCustomBackground:rect];
}

@end

In your main.m you preform the actual swizzle and exchange the implementations of drawRectCustomBackground: and drawRect: with each other. Now whenever someone calls drawRect: on the nav bar, our code inside drawRectCustomBackground: will be executed. If our code inside drawRectCustomBackground: need to execute, we call the default method implementation by calling ourselves again. But since we swapped or swizzled method implementations we are actually calling the default dectRect: method.

int main(int argc, char *argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    // swizzle the nav bar and exchange method implementations
    Method drawRectCustomBackground = 
         class_getInstanceMethod([UINavigationBar class], 
                                 @selector(drawRectCustomBackground:));
    Method drawRect = 
         class_getInstanceMethod([UINavigationBar class], 
                                 @selector(drawRect:));
    method_exchangeImplementations(drawRect, drawRectCustomBackground);
    
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

It’s important to note that the call to [self drawRectCustomBackground:rect] on the last line of drawRectCustomBackground: doesn’t actually call back to drawRectCustomBackground: because we swapped method implementations.

Method swizzling is part of the dynamism of Objective-C and the decision to swizzle should not be made lightly. You’re using the framework in a manner not originally designed for. At the same time, experimentation can lead towards diversification.