How to Draw Radial Gradients in a Calayer

How to draw radial gradients in a CALayer?

From what I understood, you just need a layer that draws a gradient, and CGContextDrawRadialGradient works perfectly for that need. And to reiterate on what you said, CAGradientLayer doesn't support radial gradients, and nothing we can do about that, except unnecessary swizzling that can be done cleanly with a CALayer subclass.

(note: the gradient drawing code was taken from here. It isn't what this answer is about.)


viewDidLoad:

GradientLayer *gradientLayer = [[GradientLayer alloc] init];
gradientLayer.frame = self.view.bounds;

[self.view.layer addSublayer:gradientLayer];

CALayer subclass:

- (instancetype)init
{
self = [super init];
if (self) {
[self setNeedsDisplay];
}
return self;
}

- (void)drawInContext:(CGContextRef)ctx
{

size_t gradLocationsNum = 2;
CGFloat gradLocations[2] = {0.0f, 1.0f};
CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.5f};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum);
CGColorSpaceRelease(colorSpace);

CGPoint gradCenter= CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ;

CGContextDrawRadialGradient (ctx, gradient, gradCenter, 0, gradCenter, gradRadius, kCGGradientDrawsAfterEndLocation);

CGGradientRelease(gradient);
}

Sample Image

Radial gradient background in Swift

Have a look at my implementation of RadialGradientLayer, and feel free to modify it

class RadialGradientLayer: CALayer {

override init(){

super.init()

needsDisplayOnBoundsChange = true
}

init(center:CGPoint,radius:CGFloat,colors:[CGColor]){

self.center = center
self.radius = radius
self.colors = colors

super.init()

}

required init(coder aDecoder: NSCoder) {

super.init()

}

var center:CGPoint = CGPointMake(50,50)
var radius:CGFloat = 20
var colors:[CGColor] = [UIColor(red: 251/255, green: 237/255, blue: 33/255, alpha: 1.0).CGColor , UIColor(red: 251/255, green: 179/255, blue: 108/255, alpha: 1.0).CGColor]

override func drawInContext(ctx: CGContext!) {

CGContextSaveGState(ctx)

var colorSpace = CGColorSpaceCreateDeviceRGB()

var locations:[CGFloat] = [0.0, 1.0]

var gradient = CGGradientCreateWithColors(colorSpace, colors, [0.0,1.0])

var startPoint = CGPointMake(0, self.bounds.height)
var endPoint = CGPointMake(self.bounds.width, self.bounds.height)

CGContextDrawRadialGradient(ctx, gradient, center, 0.0, center, radius, 0)

}

}

In my case I needed it with two colors only and if you need more colors you need to modify location array declared in drawInContext. Also after creating object from this class don't forget to call its setNeedsDisplay() otherwise it wont work. Also sometimes I needed different size gradients so thats why you have to pass radius parameter in initializer and the center point of your gradient

Draw radial background on a UIBezierPath

Here's how you can make your original code work with minimal changes.

override func draw(_ rect: CGRect) {
// [...]
// your code untouched until this line:
bezierPath.close()

// change the rest of the function to:
bezierPath.fill()

UIGraphicsGetCurrentContext()!.setBlendMode(.sourceIn)
let colors = [UIColor.green.cgColor, UIColor.blue.cgColor] as CFArray
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
let endPosition = min(frame.width, frame.height) / 2
let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
UIGraphicsGetCurrentContext()?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endPosition, options: .drawsAfterEndLocation)
}

Result:

Result

The idea is to first draw the bezier with the rays. The color does not matter here, this is only to draw alpha.

Then we draw the gradient on top of the alpha using one of Quartz' peculiar blend modes: kCGBlendModeSourceIn (see Porter-Duff alpha compositing).

This mode draws anything on top of existing alpha, replacing only the pixel's color, leaving alpha as it was. It's basically like using the current drawing's alpha as a mask.

How to cut out a hole with radial gradient in view

You can play around a little bit and get

Sample Image

To achieve such result I prepared sample code in playground - just copy-paste it and try.

import UIKit
import PlaygroundSupport

class RadialGradientLayer: CALayer {

required override init() {
super.init()
needsDisplayOnBoundsChange = true
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

required override init(layer: Any) {
super.init(layer: layer)
}

//default colors
public var colors = [UIColor.red.cgColor, UIColor.clear.cgColor]

override func draw(in ctx: CGContext) {
ctx.saveGState()

let colorSpace = CGColorSpaceCreateDeviceRGB()
var locations = [CGFloat]()
for i in 0...colors.count-1 {
locations.append(CGFloat(i) / CGFloat(colors.count))
}
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations)
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
let radius = min(bounds.width, bounds.height)
ctx.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: radius, options: CGGradientDrawingOptions(rawValue: 0))
}
}

let view = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 300))
view.backgroundColor = UIColor.green

let label = UILabel(frame: view.bounds)
label.text = "test"
label.font = UIFont.systemFont(ofSize: 30)
label.textAlignment = .center
view.addSubview(label)

let gradientLayer = RadialGradientLayer()
gradientLayer.frame = view.bounds
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradientLayer.setNeedsDisplay()

view.layer.addSublayer(gradientLayer)

view

How do I draw an angle gradient using CALayer or CoreGraphics functions?

Unfortunately, Core Graphics and Quartz only support axial (linear) and radial (circular) gradients. For an angle gradient you’ll either have to draw it manually (perhaps by drawing triangles in a circle at a resolution fine enough for the color shifts to be unnoticeable) or have a precomposed angle-gradient image that you resize and clip to your drawing needs.

Draw gradient layer in drawInRect

Using a gradient layer or drawing a gradient in draw rect is quite different. To draw the gradient in drawRect you should try something like the following:

- (void)drawRect:(CGRect)rect {
[super drawRect:rect];

UIColor *startColor = [UIColor colorWithRed:1.0f green:.0f blue:.0f alpha:1.0f];
UIColor *endColor = [UIColor colorWithRed:.0f green:1.0f blue:.0f alpha:1.0f];

NSArray *colors = @[(__bridge id)startColor.CGColor, (__bridge id)endColor.CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), (CFArrayRef)colors, nil);

CGContextDrawLinearGradient(UIGraphicsGetCurrentContext(), gradient, CGPointMake(0.0f, 0.0f), CGPointMake(self.bounds.size.width, self.bounds.size.height), kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation);
}

Overriding drawRect is on a bit lower level so you may not use most of layer calls inside it without creating a loop.



Related Topics



Leave a reply



Submit