JRG-Developer
JRG-Developer

Reputation: 12663

How can I replicate UINavigationBar's gradient colors?

I've been trying to replicate the gradient from UINavigationBar to use as a gradient on custom UIButton subclass objects on the same view.

However, I can't figure out how the colors are derived? That is, you only specify one color to set a UINavigationBar's background color- tintColor - but it creates a nice gradient with it seems at least 4 colors?

I'm really just interested in the "inner" top and bottom colors though - just inside the 1px border around the bar... the outer "border" colors do indeed appear different though.

EDIT - 1

Upon further research, it appears the HSB (instead of the RBG as first thought) values are being manipulated to get these different colors.

There is also a convenience method on UIColor to get the HSB values, which should be helpful:

getHue:saturation:brightness:alpha:

Helpful References Found So Far

HSL and HSV Wiki

UIColor Class Reference

Programmatically Lighten a Color

From the book Fundamentals of Interactive Computer Graphics

EDIT - 2

In case you were unaware that you could set a gradient for a background on a UIButton programmatically, here's some references for how to do such:

FUN WITH UIBUTTONS AND CORE ANIMATION LAYERS

Five Tips for Creating Stylish UIButtons (kudos to @cdo for providing this link)

EDIT - 3

I've put together a spreadsheet showing the original and the "inner" gradient colors (disregarding the outer most colors) in HSB values on UINavigationBar and its corresponding "back" button (titles are irrelevant and always displayed white).

Here's a link to the Google doc with the information that I've collected for a few sample colors:

https://docs.google.com/spreadsheet/ccc?key=0AnKVtzkNS9scdGVRN01pa1NQcC1hdThNbEVzQU8wRlE&usp=sharing

Note: these values were found by saving a screenshot using the retina, 3.5" iPhone Simulator (Xcode version 4.6) for iOS 6.1 and eye-dropping the HSB values using PhotoShop.

BOUNTY AWARD CRITERIA

I've opened a bounty on this question to bring more exposure to it and hopefully get a good answer. The answer that I'm looking for:

Provide a method of calculating/closely approximating (in most cases) the RGB or HSB values of the "inner top" and "inner bottom" gradient colors (see spreadsheet) created after setting a tintColor on UINavigationBar.

Bonus points awarded (above initial bounty offering) if you also provide a method for calculating the "inner top" and "inner bottom" gradient colors on the "back" button (which is similar to the navigation bar, but I've found these colors appear to be slightly "darker" usually)?

Upvotes: 5

Views: 2371

Answers (4)

danh
danh

Reputation: 62676

Thanks for the good question and the generous bounty. I went to work on this and neglected to check in to see that it was already answered acceptably. Nevertheless, it was fun building and testing the following navigation bar category, that reveals it's colors ...

//
//  UINavigationBar+colors.h

#import <UIKit/UIKit.h>

@interface UINavigationBar (Colors)

// Answer an array of colors representing the color of the reciever, starting at fromY, up to toY
- (NSArray *)colorsFromY:(NSUInteger)fromY to:(NSUInteger)toY;

@end

Link with QuartzCore.framework.

//
//  UINavigationBar+colors.m

#import "UINavigationBar+colors.h"
#import <QuartzCore/QuartzCore.h>

#define UIIMAGE_BYTES_PER_PIXEL 4u

@implementation UINavigationBar (Colors)

+ (NSData *)dataFromImage:(UIImage *)image {

    CGImageRef imageRef = [image CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    NSUInteger dataSize = height * width * UIIMAGE_BYTES_PER_PIXEL;
    unsigned char *rawData = malloc(dataSize);
    NSUInteger bytesPerRow = width * UIIMAGE_BYTES_PER_PIXEL;
    NSUInteger bitsPerComponent = 8;
    CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                                 bitsPerComponent, bytesPerRow, colorSpace,
                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);

    NSData *rtn = [NSData dataWithBytes:rawData length:dataSize];
    free(rawData);
    return rtn;
}


+ (UIColor *)colorOfImage:(UIImage *)image atX:(NSUInteger)px atY:(NSUInteger)py  {

    NSData *imgData = [self dataFromImage:image];
    if (!imgData) return nil;

    NSUInteger byteIndex = UIIMAGE_BYTES_PER_PIXEL * (image.size.width * py + px);

    unsigned char rgbaData[4];
    NSRange range = { byteIndex, 4u };
    [imgData getBytes:rgbaData range:range];
    CGFloat red   = rgbaData[0] / 255.0;
    CGFloat green = rgbaData[1] / 255.0;
    CGFloat blue  = rgbaData[2] / 255.0;
    CGFloat alpha = rgbaData[3] / 255.0;

    return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}

- (UIImage *)asImage {

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0.0);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return img;
}

- (NSArray *)colorsFromY:(NSUInteger)fromY to:(NSUInteger)toY {

    NSMutableArray *answer = [NSMutableArray array];
    UIImage *image = [self asImage];

    for (NSUInteger y = MAX(0, fromY); y < MIN(self.bounds.size.height, toY); y++) {
        [answer addObject:[self.class colorOfImage:image atX:1 atY:y]];
    }
    return [NSArray arrayWithArray:answer];
}

@end

Call it like this:

// from a view controller contained by a navigation controller...
UINavigationBar *bar = self.navigationController.navigationBar;

NSArray *colors = [bar colorsFromY:0 to:bar.bounds.size.height];
for (UIColor *color in colors) {
    NSLog(@"%@", color);
}

Upvotes: 2

Deniz Mert Edincik
Deniz Mert Edincik

Reputation: 4361

Short Answer: it is not gradient

Long Answer: After tint color applied, there is a transparent overlay image rendered on top of it.

It is called: [email protected] an it is in UIKit artwork. (uploaded here: http://cl.ly/image/2c2V3t1D1T3L)

It is 2x88 pixel image, and must be repeated horizontally over tinted background.

For back button, it is very similar, but there is also a mask to give it it's shape. UItintedBackButtonHighlight and UITintedBackButtonMask.

Upvotes: 7

user207616
user207616

Reputation:

It's hard to copy the exact behavior because it seems that Apple is calculating different for different color groups. eg. a light color is slightly darkened while a dark color is lit up.

Same for the bar button item. For some colors the difference for normal "bordered" button and "done"-style button is completely different. Sometimes not noticeable like for turquoise but definitely seeable for orange.

For creating this sample code I used PaintCode a nice tool for prototyping btw..
Copy this code in a UIView subclass or something. Or just grab the pieces code you need.

- (void)drawRect:(CGRect)rect
{
    //// General Declarations
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = UIGraphicsGetCurrentContext();

    //// Color Declarations
    UIColor* tint = [UIColor colorWithRed: 1 green: 0.66 blue: 0.329 alpha: 1];
    CGFloat tintRGBA[4];
    [tint getRed: &tintRGBA[0] green: &tintRGBA[1] blue: &tintRGBA[2] alpha: &tintRGBA[3]];

    UIColor* lighter = [UIColor colorWithRed: (tintRGBA[0] * 0.58 + 0.42) green: (tintRGBA[1] * 0.58 + 0.42) blue: (tintRGBA[2] * 0.58 + 0.42) alpha: (tintRGBA[3] * 0.58 + 0.42)];
    CGFloat lighterRGBA[4];
    [lighter getRed: &lighterRGBA[0] green: &lighterRGBA[1] blue: &lighterRGBA[2] alpha: &lighterRGBA[3]];

    UIColor* lightest = [UIColor colorWithRed: (lighterRGBA[0] * 0.55 + 0.45) green: (lighterRGBA[1] * 0.55 + 0.45) blue: (lighterRGBA[2] * 0.55 + 0.45) alpha: (lighterRGBA[3] * 0.55 + 0.45)];
    UIColor* darker = [UIColor colorWithRed: (tintRGBA[0] * 0.92) green: (tintRGBA[1] * 0.92) blue: (tintRGBA[2] * 0.92) alpha: (tintRGBA[3] * 0.92 + 0.08)];
    CGFloat darkerRGBA[4];
    [darker getRed: &darkerRGBA[0] green: &darkerRGBA[1] blue: &darkerRGBA[2] alpha: &darkerRGBA[3]];

    UIColor* darkest = [UIColor colorWithRed: (darkerRGBA[0] * 0.65) green: (darkerRGBA[1] * 0.65) blue: (darkerRGBA[2] * 0.65) alpha: (darkerRGBA[3] * 0.65 + 0.35)];

    //// Gradient Declarations
    NSArray* gradientColors = [NSArray arrayWithObjects:
                               (id)lighter.CGColor,
                               (id)darker.CGColor, nil];
    CGFloat gradientLocations[] = {0, 1};
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)gradientColors, gradientLocations);


    //// top Drawing
    UIBezierPath* topPath = [UIBezierPath bezierPathWithRect: CGRectMake(0, 0, rect.size.width, 1)];
    [lightest setFill];
    [topPath fill];

    //// theGradient Drawing
    UIBezierPath* theGradientPath = [UIBezierPath bezierPathWithRect: CGRectMake(0, 1, rect.size.width, rect.size.height - 1.0f)];
    CGContextSaveGState(context);
    [theGradientPath addClip];
    CGContextDrawLinearGradient(context, gradient, CGPointMake(50, 1), CGPointMake(50, rect.size.height-1.0f), 0);
    CGContextRestoreGState(context);

    //// bottom Drawing
    UIBezierPath* bottomPath = [UIBezierPath bezierPathWithRect: CGRectMake(0, rect.size.height-1.0f, rect.size.width, 1)];
    [darkest setFill];
    [bottomPath fill];


    //// Cleanup
    CGGradientRelease(gradient);
    CGColorSpaceRelease(colorSpace);
}

Upvotes: 2

ozz
ozz

Reputation: 1137

UIButton takes a single tintColor property but that doesn't mean it isn't computing other colors to use in the gradient behind the scenes. Try this tutorial.

Upvotes: 0

Related Questions