So, I was using a category to make our UINavBar have a clever gradient. I preferred to draw the gradient rather than use an image, so we can keep everything in code, cut down on file loading, resolution independence, yada yada yada. So, here’s how I did it:

#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

@implementation UINavigationBar (CustomGradient)

- (void) drawRect:(CGRect)rect{
    [super drawRect:rect];
    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = self.bounds;
    UIColor *startColor = UIColorFromRGB(0x0d3660);
    UIColor *endColor = UIColorFromRGB(0x080f1d);
    gradient.colors = [NSArray arrayWithObjects:(id)[startColor CGColor], (id)[endColor CGColor], nil];
    [self.layer insertSublayer:gradient atIndex:0];
    self.tintColor = UIColorFromRGB(0x00284B);
}

@end

The macro UIColorFromRGB is great for quickly setting colors from hex values. Also, you have to be sure to set the “tint” color to something appropriate, otherwise your nav buttons will retain the default look.

Now this caused an interesting problem: invisible buttons! UIBarButtonItem ’s would appear when first loaded, but disappear after returning from drill-down view controllers. The buttons would still work – all the normal events fired for them, but they weren’t rendered at all. Eventually, I found a way around that. Here’s the revised function:

- (void) drawRect:(CGRect)rect{
    [super drawRect:rect];
    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = self.bounds;
    UIColor *startColor = UIColorFromRGB(0x0d3660);
    UIColor *endColor = UIColorFromRGB(0x080f1d);
    gradient.colors = [NSArray arrayWithObjects:(id)[startColor CGColor], (id)[endColor CGColor], nil];

    UIGraphicsBeginImageContext(rect.size);
    [gradient renderInContext: UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    [image drawInRect:rect];
    self.tintColor = UIColorFromRGB(0x00284B);
}

@end

This way creates a new context, renders the gradient to that context, then pulls the rendered image into a UIImage it can render into the UINavigationBar view. Ultimately, BarButtonItems must be rendered as sublayers, and rendered at layer zero. When the NavigationBar is first loaded, this is fine: the BarButton gets added after the initial rendering, bumping the gradient up to index one. But after a pushViewController / popViewController cycle, the gradient gets re-added as a layer at index 0, thus putting it in front of any buttons you’ve added since the initial rendering.

Drawing in image in the UINavigationBar doesn’t affect how it renders sublayers, so the buttons stay visible in this context.

All in all, it probably would have been easier to just use an image. Lesson learned. :/

side note: Can’t remember where I found the UIColorFromRGB macro, but I certainly didn’t invent it. Apologies to whoever did, but the relevant Google search is clogged with unattributed copies. Good on ya, though!