Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Gradient along a bezier path (using CALayers)

I have a relatively straight forward implementation of a progress view set up with CALayer objects. The progress view itself is a subview of UIView.

Here is the code that sets up the progress ring:

        self.progressRingLayer = CAShapeLayer()

        let innerRect = CGRectInset(bounds, CGFloat(self.lineWidth) / 2, CGFloat(self.lineWidth) / 2)
        let innerPath = UIBezierPath(ovalInRect: innerRect)

        self.progressRingLayer.path = innerPath.CGPath
        self.progressRingLayer.fillColor = UIColor.clearColor().CGColor
        self.progressRingLayer.strokeColor = kProgressColor.CGColor
        self.progressRingLayer.anchorPoint = CGPointMake(0.5, 0.5)
        self.progressRingLayer.transform = CATransform3DRotate(self.progressRingLayer.transform, (CGFloat(M_PI))*1, 0, 0, 1)
        self.progressRingLayer.lineCap = kCALineCapRound
        self.progressRingLayer.lineWidth = CGFloat(self.lineWidth)

        self.layer.addSublayer(self.progressRingLayer)

What I am trying to do now is add a gradient to the progressRingLayer that follows (or bends with) the path. I have been successful in adding a linear gradient to the fill, but not to just the path.

Here is an example of what effect I want:

enter image description here

So far everything I have found requires a bunch of additional steps with CoreGraphics and CGContext that don't quite fit with my implementation. Any help would be great, thanks!

like image 693
Kyle Begeman Avatar asked Jan 13 '15 20:01

Kyle Begeman


1 Answers

What I would do is draw a gradient layer, then draw on top of that a layer that is black with the arc erased.

Here's my attempt at roughly the image you provided (I omitted the white label in the center, but that's trivial):

enter image description here

And here's the code that generated it:

let r = CGRectMake(100,100,130,100)

let g = CAGradientLayer()
g.frame = r
let c1 = UIColor(
    red: 151.0/255.0, green: 81.0/255.0, blue: 227.0/255.0, alpha: 1)
let c2 = UIColor(
    red: 36.0/255.0, green: 176.0/255.0, blue: 233.0/255.0, alpha: 1)
g.colors = [c1.CGColor as AnyObject, c2.CGColor as AnyObject];
self.view.layer.addSublayer(g)

let percent = CGFloat(0.64) // percentage of circle
UIGraphicsBeginImageContextWithOptions(r.size, false, 0)
let con = UIGraphicsGetCurrentContext()
CGContextFillRect(con, CGRect(origin: CGPoint(), size: r.size))
CGContextSetLineWidth(con, 5)
CGContextSetLineCap(con, kCGLineCapRound)
CGContextSetBlendMode(con, kCGBlendModeClear)
let pi = CGFloat(M_PI)
CGContextAddArc(con, r.size.width/2.0, r.size.height/2.0, 30, 
    -pi/2.0, -pi/2.0 + percent*pi*2.0, 0)
CGContextStrokePath(con)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let b = CALayer()
b.frame = r
b.contents = im.CGImage
self.view.layer.addSublayer(b)

The gradient layer (the first part of the code) is just a "serving suggestion". If that is not the gradient you want, you can design your own. You could draw it in Photoshop and use an image as the content of the gradient layer. Or you could make an "angular" layer in code, using third-party code such as https://github.com/paiv/AngleGradientLayer. The point of the example is merely to show how it is possible to "erase" an arc in a black layer so as to reveal the gradient concealed behind it, and thus appear to paint with a gradient.

like image 154
matt Avatar answered Oct 30 '22 06:10

matt