Sazzad Hissain Khan
Sazzad Hissain Khan

Reputation: 40156

How to fill a bezier path with gradient color

I have a UIBezierPath inside my custom UIView draw(_ rect: CGRect) function. I would like to fill the path with a gradient color. Please can anybody guide me how can I do that.

I need to fill the clip with a gradient color and then stroke the path with black color.

There are some posts in SO which does not solve the problem. For example Swift: Gradient along a bezier path (using CALayers) this post guides how to draw on a layer in UIView but not in a UIBezierPath.

NB: I am working on Swift-3

Upvotes: 22

Views: 20605

Answers (4)

clearlight
clearlight

Reputation: 12615

Swift 5 version of @AlbertRenshaw's (ObjC) answer, and wrapped in a UIView class. What it displays looks spherical but on it's side. A CGAffineTransform could be applied to rotate it 90º for a cool effect.

import UIKit

class SphereView : UIView {
    override func draw(_ rect: CGRect) {
        super.draw(rect)

        let centerPoint = CGPoint(x: frame.width / 2, y: frame.height / 2)
        let radius = frame.height / 2
        let circlePath = UIBezierPath(arcCenter: centerPoint, 
                                      radius: radius, 
                                      startAngle: 0, 
                                      endAngle: .pi * 2, 
                                      clockwise: true)

        let shapeLayer = CAShapeLayer()
        shapeLayer.path         = circlePath.cgPath;
        shapeLayer.strokeColor  = UIColor.green.cgColor
        shapeLayer.lineWidth    = 2.0;

        let gradientBounds = CGRectMake(
            circlePath.bounds.origin.x    - shapeLayer.lineWidth,
            circlePath.bounds.origin.y    - shapeLayer.lineWidth,
            circlePath.bounds.size.width  + shapeLayer.lineWidth * 2.0,
            circlePath.bounds.size.height + shapeLayer.lineWidth * 2.0)
        
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame  = gradientBounds;
        gradientLayer.bounds = gradientBounds;
        gradientLayer.colors = [UIColor.red, UIColor.blue].map { $0.cgColor }
        gradientLayer.mask = shapeLayer;
        self.layer.addSublayer(gradientLayer)
    }
}

Usage in a UIViewController:

override func viewDidLoad() {
    view.backgroundColor = .white
    let sphereView = SphereView()
    sphereView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(sphereView)
    NSLayoutConstraint.activate([
        sphereView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        sphereView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        sphereView.widthAnchor.constraint(equalToConstant: 150),
        sphereView.heightAnchor.constraint(equalToConstant: 150),
    ])
}

Upvotes: 0

Albert Renshaw
Albert Renshaw

Reputation: 17892

The other answers here don't seem to work (At least in newer iOS versions?)

Here's a working example of a bezier path being created and the stroke being a gradient stroke, adjust the CGPoints and CGColors as needed:

        CGPoint start = CGPointMake(300, 400);
        CGPoint end = CGPointMake(350, 400);
        CGPoint control = CGPointMake(300, 365);
        
        UIBezierPath *path = [UIBezierPath new];
        [path moveToPoint:start];
        [path addQuadCurveToPoint:end controlPoint:control];
        
        CAShapeLayer *shapeLayer = [CAShapeLayer new];
        shapeLayer.path = path.CGPath;
        shapeLayer.strokeColor = [UIColor whiteColor].CGColor;
        shapeLayer.fillColor = [UIColor clearColor].CGColor;
        shapeLayer.lineWidth = 2.0;
        //[self.view.layer addSublayer:shapeLayer];
        
        CGRect gradientBounds = CGRectMake(path.bounds.origin.x-shapeLayer.lineWidth, path.bounds.origin.y-shapeLayer.lineWidth, path.bounds.size.width+shapeLayer.lineWidth*2.0, path.bounds.size.height+shapeLayer.lineWidth*2.0);
        CAGradientLayer *gradientLayer = [CAGradientLayer new];
        gradientLayer.frame = gradientBounds;
        gradientLayer.bounds = gradientBounds;
        gradientLayer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor];
        gradientLayer.mask = shapeLayer;
        [self.view.layer addSublayer:gradientLayer];

Upvotes: 3

Robin Stewart
Robin Stewart

Reputation: 3883

You can do this directly in Core Graphics without using CALayer classes. Use bezierPath.addClip() to set the bezier path as the clipping region. Any subsequent drawing commands will be masked to that region.

I use this wrapper function in one of my projects:

func drawLinearGradient(inside path:UIBezierPath, start:CGPoint, end:CGPoint, colors:[UIColor])
{
    guard let ctx = UIGraphicsGetCurrentContext() else { return }

    ctx.saveGState()
    defer { ctx.restoreGState() } // clean up graphics state changes when the method returns

    path.addClip() // use the path as the clipping region

    let cgColors = colors.map({ $0.cgColor })
    guard let gradient = CGGradient(colorsSpace: nil, colors: cgColors as CFArray, locations: nil)
        else { return }

    ctx.drawLinearGradient(gradient, start: start, end: end, options: [])
}

Upvotes: 17

Matt
Matt

Reputation: 1761

To answer this question of yours,

I have a UIBezierPath inside my custom UIView draw(_ rect: CGRect) function. I would like to fill the path with a gradient color.

Lets say you have an oval path,

let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 100, height: 100))

To create a gradient,

let gradient = CAGradientLayer()
gradient.frame = path.bounds
gradient.colors = [UIColor.magenta.cgColor, UIColor.cyan.cgColor]

We need a mask layer for gradient,

let shapeMask = CAShapeLayer()
shapeMask.path = path.cgPath

Now set this shapeLayer as mask of gradient layer and add it to view's layer as subLayer

gradient.mask = shapeMask
yourCustomView.layer.addSublayer(gradient)

Update Create a base layer with stroke and add before creating gradient layer.

let shape = CAShapeLayer()
shape.path = path.cgPath
shape.lineWidth = 2.0
shape.strokeColor = UIColor.black.cgColor
self.view.layer.addSublayer(shape)

let gradient = CAGradientLayer()
gradient.frame = path.bounds
gradient.colors = [UIColor.magenta.cgColor, UIColor.cyan.cgColor]

let shapeMask = CAShapeLayer()
shapeMask.path = path.cgPath
gradient.mask = shapeMask

self.view.layer.addSublayer(gradient)

Upvotes: 34

Related Questions