Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UINavigationBar tintColor with gradient

I'd like to change programmaticaly the tintColor of a UINavigationBar and keep the gradient as in Interface Builder.

When I change the tintColor in my code, the gradient disappears but when I change the tintColor in Interface Builder, the gradient is kept.

Any ideas?

like image 688
pbernery Avatar asked Jan 30 '09 09:01

pbernery


3 Answers

So this is an old question, but I stumbled upon it when I was searching for something that sounds like this question, but is different. In hopes that it will help someone else (or that someone else will show me a better way)...

How do you implement a custom gradient on a UINavigationBar?

First, let's get our custom gradient the way we normally would (As a CALayer):

- (CALayer *)gradientBGLayerForBounds:(CGRect)bounds
{
    CAGradientLayer * gradientBG = [CAGradientLayer layer];
    gradientBG.frame = bounds;
    gradientBG.colors = @[ (id)[[UIColor redColor] CGColor], (id)[[UIColor purpleColor] CGColor] ];
    return gradientBG;
}

There we go, a gradient that looks like a questionable make-up choice of a punk rocker, which is good, as this code is for the hypothetical app I Have A Punk-Rocker With Questionable Tastes In My Pocket. That name me be a little too long..

Now I want this layer on my UINavigationBar, which should be easy ya? Just add it as a sublayer, right? The problem here is it will cover up the wonderful buttons and stuff that the UINavigationController gives you. That's bad.

Instead let's look at the wonderful convenience method we have in iOS5+ for altering the appearance of all the UINavigationBar(s) in our app:

[[UINavigationBar appearance] setBackgroundImage:SOME_IMAGE
                                   forBarMetrics:UIBarMetricsDefault];

The only problem here is we don't have an UIImage, we have a CALayer. What's a punk-rocker enthusiast to do?

CALayer * bgGradientLayer = [self gradientBGLayerForBounds:ONE_OF_YOUR_NAV_CONTROLLERS.navigationBar.bounds];
UIGraphicsBeginImageContext(bgGradientLayer.bounds.size);
[bgGradientLayer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * bgAsImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

So now we've converted our CALayer to an UIImage (hopefully), so all that remains is to set it:

if (bgAsImage != nil)
{
    [[UINavigationBar appearance] setBackgroundImage:bgAsImage
                                       forBarMetrics:UIBarMetricsDefault];
}
else
{
    NSLog(@"Failded to create gradient bg image, user will see standard tint color gradient.");
}

What I like about this solution is

  • the centralized code (in the AppDelegate) for all UINavigationBar(s) in my app
  • tintColor will still be honored for all the UINavigation buttons (back, edit, etc.)
  • if the UIImage creation fails my UINavigationBar(s) will still honor the tintColor
  • I don't have to depend on an actual image resource / easy to update

But I'm still not convinced it's the best way. So, enjoy if it helps you, let me know how to improve it if you can.

~ Thanks

like image 67
Ryan Crews Avatar answered Oct 23 '22 10:10

Ryan Crews


Set the barStyle to UIBarStyleBlackTranslucent Set the tintColor like this:

navigationBar.tintColor = [UIColor colorWithRed:.7 green:.5 blue:.2 alpha:1];

This will set the appropriate gradient. Use whatever combination you need.

like image 31
carlos Avatar answered Oct 23 '22 11:10

carlos


I just wrote it as UIImage extension for Swift 2.0. Maybe it's of some use.

extension UIImage {

    class func convertGradientToImage(colors: [UIColor], frame: CGRect) -> UIImage {

        // start with a CAGradientLayer
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = frame

        // add colors as CGCologRef to a new array and calculate the distances
        var colorsRef : [CGColorRef] = []
        var locations : [NSNumber] = []

        for i in 0 ... colors.count-1 {
            colorsRef.append(colors[i].CGColor as CGColorRef)
            locations.append(Float(i)/Float(colors.count))
        }

        gradientLayer.colors = colorsRef
        gradientLayer.locations = locations

        // now build a UIImage from the gradient
        UIGraphicsBeginImageContext(gradientLayer.bounds.size)
        gradientLayer.renderInContext(UIGraphicsGetCurrentContext()!)
        let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        // return the gradient image
        return gradientImage
    }
}

Call it like this:

let colors = [
    UIColor.blueColor(),
    UIColor.greenColor()
    // and many more if you wish
]
let gradientImage = UIImage.convertGradientToImage(colors, frame: navigationBar.bounds)
navigationBar.setBackgroundImage(gradientImage, forBarMetrics: .Default)
like image 1
RyuX51 Avatar answered Oct 23 '22 10:10

RyuX51