Devin's Heaven

Create A Custom UINavigationBar Style

Apple's UINavigation framework is the easiest way to build practically any standard UI app but the problem is you can't customize the aesthetic design of the bar other than the tint. You're stuck with the same glossy navigation bar that you see in every other application. Boring, right? Some applications, notably Pastebot, Weet and most recently Path have effectively layered on their own UI that fit into their application nicely. I'll show you exactly how to create your own custom navigation UI using some objective-c magic.

custom_nav.png

So how do you go about creating a custom look and feel to your application? Do you need to roll your own UINavigationController? Thankfully, no. With method swizzling you can substitute function implementations for certain functions like drawRect: and easily create a very custom look. Swizzling methods creates a smaller code footprint compared to subclassing the current structure or implementing your own. I’ve created a working example of how to achieve a unique navigation style with my open source framework (see the demo project) but I’ll highlight a few snippets here to give you an idea of how things work.

Swizzling the Navigation Bar

For UINavigationBar and UIToolbar, drawRect is responsible for creating the background so swizzling this function will allow us to provide a new function to draw the background. By swizzling the method, we can also call the default implementation of a function too which is really nice if you only want to change the look of one style and not the others (ie. the default style and not the translucent style ).

#import "objc/runtime.h"
#import "objc/message.h"

void Swizzle(Class c, SEL orig, SEL new){
  Method origMethod = class_getInstanceMethod(c, orig);
  Method newMethod = class_getInstanceMethod(c, new);
  if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
    class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
  else
    method_exchangeImplementations(origMethod, newMethod);
}

int main(int argc, char *argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  Swizzle([UINavigationBar class], @selector(drawRect:), @selector(TKdrawRect:));
  int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate");
  [pool release];
  return retVal;
}

Use this snippet of code in your main.m file for your project. The UINavigationBar’s drawRect: selector will be replaced as soon as the application launches. Now we need to go ahead and create a category (again, more objective-c trickery) for UINavigationBar to append a new draw function to the class.

#import "UINavigationBar+TKCategory.h"
@implementation UINavigationBar (TKCategory)
- (void)TKdrawRect:(CGRect)rect {

  if (self.barStyle == UIBarStyleDefault) {

    NSArray *colors = [NSArray arrayWithObjects:
      [UIColor colorWithRed:176/255.0 green:188/255.0 blue:204/255.0 alpha:1],
      [UIColor colorWithRed:109/255.0 green:132/255.0 blue:162/255.0 alpha:1],nil];

    [UIView drawGradientInRect:rect withColors:colors];
    [[UIColor colorWithRed:45/255.0 green:54/255.0 blue:66/255.0 alpha:1] set];
    CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, rect.size.height-1, rect.size.width, 1));
    [[UIColor colorWithRed:205/255.0 green:213/255.0 blue:223/255.0 alpha:1] set];
    CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, rect.size.width, 1));
    [[UIColor colorWithRed:158/255.0 green:173/255.0 blue:193/255.0 alpha:1] set];
    CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, rect.size.height-2, rect.size.width, 1));
    return;
  }
  [self TKdrawRect:rect]; // Calls default implementation
}
@end

Setup Custom Buttons

By now you’ve realized that changing the navigation bar did little to change the appearance of the buttons. UsingĀ - (id)initWithCustomView:(UIView *)customView to create custom buttons is fine but what about those pesky back buttons? They need to fit in with the new UI look too. To create a custom back button, we can subclass UIBarButtonItem and swizzle the UINavigationController push method. The subclassed bar button I’ve created closely imitates the current bar buttons so you can easily swap interface art.

Swizzle([UINavigationController class], @selector(pushViewController:animated:),
@selector(TKpushViewController:animated:));

Add this snippet to the main.m file to swizzle the UINavigationController’s method so that it sets up the back button properly.

@implementation UINavigationController (TKCategory)
- (void) TKpushViewController:(UIViewController *)viewController animated:(BOOL)animated{

  if(self.navigationBar.barStyle == UIBarStyleDefault
  && [self.viewControllers count] > 0
  && viewController.navigationItem.leftBarButtonItem == nil
  && [self.topViewController isKindOfClass:NSClassFromString(@"TKViewController")]){

    TKViewController *vc = (TKViewController*)self.topViewController;
    if(vc.tkBackButton){

      [vc.tkBackButton setStyle:TKBarButtonItemStyleBack];
      [vc.tkBackButton setTarget:self.topViewController.navigationController action:@selector(popViewControllerAnimated:)];
      viewController.navigationItem.leftBarButtonItem = vc.tkBackButton;
    }
  }

  [self TKpushViewController:viewController animated:animated];
}
@end

This function will set the left navigation item of the new controller if the previous controller has setup a custom back button. The tkBackButton property is declared in TKViewController.

As a mentioned earlier, an important aspect was to provide a solution that worked as seamless as possible. The solution I’ve provided doesn’t require subclassing UINavigationController or for you to implement your own navigation controller. Although I needed to subclass UIViewController for a back button, you don’t have to provide a lot of code to existing classes to comply with the new changes. As a developer, the goal is always to cleanly leverage existing frameworks as much as possible before creating your own solution.

Published On Date

Filed Under

Latest Comment

Wy would you buy a Ferrari if you can't drive one? As a developer, you must assume the risks that you take, and decide what is better for yo..., read more.

24 Comments

Next Post Previous Post
Page: 1234567820