How can I replicate UINavigationBar's gradient colors?

后端 未结 4 1837
太阳男子
太阳男子 2021-02-06 13:13

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

How

相关标签:
4条回答
  • 2021-02-06 13:54

    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);
    }
    
    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2021-02-06 14:09

    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);
    }
    
    0 讨论(0)
  • 2021-02-06 14:11

    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: UITintedTopBarHighlight@2x.png 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.

    0 讨论(0)
提交回复
热议问题