Reputation: 12663
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
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
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
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
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
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