CAGradientLayer Explained

How to natively draw gradients on iOS and macOS with CAGradientLayer

iOS and macOS have built in support for drawing gradients in QuartzCore with the CAGradientLayer class, no custom CoreGraphics drawing in -drawRect: with CGContextDrawLinearGradient necessary.

Another advantage of CAGradientLayer over using CoreGraphics, besides the convenience, is that CAGradientLayer is rendered directly by the GPU itself. Compared to drawing a gradient with CoreGraphics, which will draw into a buffer on the CPU and then send that to the GPU.

Linear (Axial) Gradients

To create a linear gradient from one color to another (or several) the layer's type must be set to kCAGradientLayerAxial (.axial). The colors must be CGColor's, not UIColors. If you forgot this the color object is just ignored completely.

// Objective C

gradientLayer.colors =
@[
    (id)[UIColor colorWithRed: 48.0/255.0 green: 35.0/255.0 blue: 174.0/255.0 alpha: 1.0].CGColor,
    (id)[UIColor colorWithRed: 200.0/255.0 green: 109.0/255.0 blue: 215.0/255.0 alpha: 1.0].CGColor
];
// Swift

gradientLayer.colors =
[
    UIColor(red: 48.0/255.0, green: 35.0/255.0, blue: 174.0/255.0, alpha: 1.0).cgColor,
    UIColor(red: 200.0/255.0, green: 109.0/255.0, blue: 215.0/255.0, alpha: 1.0).cgColor
]

Start Point and End Point

The start and end point of the gradient can be changed to get different effects.
For gradients the X and Y axis go from 0 to 1, across their length. So a start point of (x:0, y:0) is the top left corner.

By default the start point is (0.5, 0.0), half way across the horizontal and at the top vertically, and the end point is the bottom middle of the view (0.5, 1.0).

The code to create this example horizontal gradient is:

// Objective C

// Set type (Axial is already the default value)
gradientLayer.type = kCAGradientLayerAxial;
// Set the colors (these need to be CGColor's, not UIColor's)
gradientLayer.colors =
@[
    (id)[UIColor colorWithRed: 48.0/255.0 green: 35.0/255.0 blue: 174.0/255.0 alpha: 1.0].CGColor,
    (id)[UIColor colorWithRed: 200.0/255.0 green: 109.0/255.0 blue: 215.0/255.0 alpha: 1.0].CGColor
];
// Set the start and end points
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 0);
// Swift

// Set type (Axial is already the default value)
gradientLayer.type = CAGradientLayerType.axial
// Set the colors (these need to be CGColor's, not UIColor's)
gradientLayer.colors =
[
    UIColor(red: 48.0/255.0, green: 35.0/255.0, blue: 174.0/255.0, alpha: 1.0).cgColor,
    UIColor(red: 200.0/255.0, green: 109.0/255.0, blue: 215.0/255.0, alpha: 1.0).cgColor
]
// Set the start and end points
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 0)

Multiple Colors and Positions

Most of my uses for gradients only involve 2 colors, but the colors property is an array of colors are you can add as many as you want.

gradientLayer.colors =
@[
    (id)[UIColor blueColor].CGColor,
    (id)[UIColor orangeColor].CGColor,
    (id)[UIColor greenColor].CGColor,
    (id)[UIColor redColor].CGColor,
    (id)[UIColor purpleColor].CGColor
];

The locations property can be used to control how the colors are distributed across the layer.

// ObjC
gradientLayer.locations = @[
    @0,   // blueColor
    @0.1, // orangeColor
    @0.6, // greenColor
    @0.7, // redColor
    @1    // purpleColor
];
// Swift
gradientLayer.locations = [
    0,   // blueColor
    0.1, // orangeColor
    0.6, // greenColor
    0.7, // redColor
    1    // purpleColor
]

Radial Gradients

To create a radial gradient out from one point the layer's type must be set to kCAGradientLayerRadial (.radial).

The start and end point are important, as the endPoint's x and y define the end of the second color.

With a startPoint in the center (0.5, 0.5), an endPoint x of 0 or 1 will draw from the center to the outer edge of the view horizontally.
An endPoint Y of 1 or 0 will draw the gradient to the height of the view.

The code to create this example gradient is:

// Objective C

// Set the type
gradientLayer.type = kCAGradientLayerRadial;
gradientLayer.colors =
@[
    (id)[UIColor colorWithRed: 0.0/255.0 green: 101.0/255.0 blue: 255.0/255.0 alpha: 1.0].CGColor,
    (id)[UIColor colorWithRed: 0.0/255.0 green: 40.0/255.0 blue: 101.0/255.0 alpha: 1.0].CGColor
];
// Start in the center
gradientLayer.startPoint = CGPointMake(0.5, 0.5);
// End at the outer edge of the view
gradientLayer.endPoint = CGPointMake(0, 0.75);
// Swift

// Set type to radial
gradientLayer.type = CAGradientLayerType.radial
// Set the colors
gradientLayer.colors =
[
    UIColor(red: 0.0/255.0, green: 101.0/255.0, blue: 255.0/255.0, alpha: 1.0).cgColor,
    UIColor(red: 0.0/255.0, green: 40.0/255.0, blue: 101.0/255.0, alpha: 1.0).cgColor
]
// Start point of first color in the middle of the view
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
// End points to the edges of the view
gradientLayer.endPoint = CGPoint(x: 0, y: 0.75)

Angular (Conic) Gradients

Whereas linear and radial gradients have been part of the iOS SDK forever (iOS 3.0 & 3.2 respectively & macOS 10.6) angular, or conic as CoreAnimation calls them, gradients were only added in iOS 12.0 (macOS 10.14).

The startPoint is the point at which the gradient is drawn around.
The endPoint is the point the start/end line draws aiming at.

The code to create the example angular gradient is:

// Objective C

gradientLayer.type = kCAGradientLayerConic;
// Set the colors
gradientLayer.colors =
@[
    (id)[UIColor blueColor].CGColor,
    (id)[UIColor colorWithRed: 50.0/255.0 green: 251.0/255.0 blue: 255.0/255.0 alpha: 1.0].CGColor,
    (id)[UIColor blackColor].CGColor
];
// Start point of first color in the middle of the view
gradientLayer.startPoint = CGPointMake(0.5, 0.5);
// End points to the edges of the view
gradientLayer.endPoint = CGPointMake(0.5, 0);
// Swift

gradientLayer.type = CAGradientLayerType.conic
gradientLayer.colors =
[
    UIColor.blue,
    UIColor(red: 50.0/255.0, green: 251.0/255.0, blue: 255.0/255.0, alpha: 1.0).cgColor,
    UIColor.black
]
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)

UIView Wrapper

As layers are a little annoying to work with in UIKit, with implicit animations and separate layout, I almost always use it via wrapping it in a custom UIView subclass.
You can actually change the underlying type of a UIView by overriding the +(Class)layerClass class method and returning any CALayer subclass.

Objective C Subclass. Redefining the layer property means view.layer will happily return the correct layer class, and @dynamic layer; tells it not to auto-synthesize anything for that deceleration.

// KHGradientView.h
@import UIKit;
@import QuartzCore;

@interface KHGradientView : UIView
@property(nonatomic, readonly, strong) CAGradientLayer *layer;
@end


// KHGradientView.m
#import "KHGradientView.h"

@implementation KHGradientView
@dynamic layer;

+(Class)layerClass{
    return [CAGradientLayer class];
}

@end
// Swift Subclass

import UIKit

class KHGradientView: UIView {
    override open class var layerClass: AnyClass {
       return CAGradientLayer.classForCoder()
    }
    var gradientLayer : CAGradientLayer {
        return self.layer as! CAGradientLayer
    }
}

It's incredibly minimal, but makes things much easier in the rest of the codebase.

Example Project

Example linear, radial, and angular gradients

The example code Xcode Project to produce the opening example image is available on Github: