I am trying to draw a circular shaped gradient.
let backgroundView:UIView = UIView()
let backgroundLayer:CAShapeLayer = CAShapeLayer()
let gradient:CAGradientLayer = CAGradientLayer()
...
backgroundLayer.frame = CGRectMake(0, 0, backgroundDiameter, backgroundDiameter)
backgroundLayer.backgroundColor = UIColor.clearColor().CGColor
backgroundLayer.strokeColor = backgroundStrokeColor
backgroundLayer.fillColor = backgroundFillColor
gradient.colors = [UIColor(red: 0.5, green: 0.5, blue: 0.9, alpha: 1.0).CGColor,
UIColor(red: 0.9, green: 0.9, blue: 0.3, alpha: 1.0).CGColor]
gradient.locations = [0.01, 0.8]
gradient.frame = backgroundLayer.frame
backgroundView.frame = CGRectMake(0, 0, backgroundDiameter, backgroundDiameter)
backgroundView.backgroundColor = UIColor.clearColor()
backgroundView.center = ringControlCenter
backgroundLayer.insertSublayer(gradient, atIndex: 1)
backgroundLayer.path = CGPathCreateWithEllipseInRect(backgroundLayer.frame, nil)
backgroundView.layer.addSublayer(backgroundLayer)
self.addSubview(backgroundView)
However the gradient does not seem to be impacted by:
backgroundLayer.path = CGPathCreateWithEllipseInRect(backgroundLayer.frame, nil)
And still have its initial shape.
Is there a way to mask the gradient with an ellipse shaped layer without using CGContext* instructions?
Thank you,
MG
I've translated Leo library's in Swift 3.x , in other words , the good WCGradientCircleLayer writed in objective-C (it work exactly as aspected)
import UIKit
class WCGraintCircleLayer: CALayer {
override init () {
super.init()
}
convenience init(bounds:CGRect,position:CGPoint,fromColor:UIColor,toColor:UIColor,linewidth:CGFloat,toValue:CGFloat) {
self.init()
self.bounds = bounds
self.position = position
let colors : [UIColor] = self.graint(fromColor: fromColor, toColor:toColor, count:4)
for i in 0..<colors.count-1 {
let graint = CAGradientLayer()
graint.bounds = CGRect(origin:CGPoint.zero, size: CGSize(width:bounds.width/2,height:bounds.height/2))
let valuePoint = self.positionArrayWith(bounds: self.bounds)[i]
graint.position = valuePoint
print("iesimo graint position: \(graint.position)")
let fromColor = colors[i]
let toColor = colors[i+1]
let colors : [CGColor] = [fromColor.cgColor,toColor.cgColor]
let stopOne: CGFloat = 0.0
let stopTwo: CGFloat = 1.0
let locations : [CGFloat] = [stopOne,stopTwo]
graint.colors = colors
graint.locations = locations as [NSNumber]? // with Swift 2 and Swift 3 you can cast directly a `CGFloat` value to `NSNumber` and back
graint.startPoint = self.startPoints()[i]
graint.endPoint = self.endPoints()[i]
self.addSublayer(graint)
//Set mask
let shapelayer = CAShapeLayer()
let rect = CGRect(origin:CGPoint.zero,size:CGSize(width:self.bounds.width - 2 * linewidth,height: self.bounds.height - 2 * linewidth))
shapelayer.bounds = rect
shapelayer.position = CGPoint(x:self.bounds.width/2,y: self.bounds.height/2)
shapelayer.strokeColor = UIColor.blue.cgColor
shapelayer.fillColor = UIColor.clear.cgColor
shapelayer.path = UIBezierPath(roundedRect: rect, cornerRadius: rect.width/2).cgPath
shapelayer.lineWidth = linewidth
shapelayer.lineCap = kCALineCapRound
shapelayer.strokeStart = 0.010
let finalValue = (toValue*0.99)
shapelayer.strokeEnd = finalValue//0.99;
self.mask = shapelayer
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layerWithWithBounds(bounds:CGRect, position:CGPoint, fromColor:UIColor, toColor:UIColor, linewidth : CGFloat,toValue:CGFloat) -> WCGraintCircleLayer {
let layer = WCGraintCircleLayer(bounds: bounds,position: position,fromColor:fromColor, toColor: toColor,linewidth: linewidth,toValue:toValue )
return layer
}
func graint(fromColor:UIColor, toColor:UIColor, count:Int) -> [UIColor]{
var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)
var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)
var result : [UIColor]! = [UIColor]()
for i in 0...count {
let oneR:CGFloat = fromR + (toR - fromR)/CGFloat(count) * CGFloat(i)
let oneG : CGFloat = fromG + (toG - fromG)/CGFloat(count) * CGFloat(i)
let oneB : CGFloat = fromB + (toB - fromB)/CGFloat(count) * CGFloat(i)
let oneAlpha : CGFloat = fromAlpha + (toAlpha - fromAlpha)/CGFloat(count) * CGFloat(i)
let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
result.append(oneColor)
print(oneColor)
}
return result
}
func positionArrayWith(bounds:CGRect) -> [CGPoint]{
let first = CGPoint(x:(bounds.width/4)*3,y: (bounds.height/4)*1)
let second = CGPoint(x:(bounds.width/4)*3,y: (bounds.height/4)*3)
let third = CGPoint(x:(bounds.width/4)*1,y: (bounds.height/4)*3)
let fourth = CGPoint(x:(bounds.width/4)*1,y: (bounds.height/4)*1)
print([first,second,third,fourth])
return [first,second,third,fourth]
}
func startPoints() -> [CGPoint] {
return [CGPoint.zero,CGPoint(x:1,y:0),CGPoint(x:1,y:1),CGPoint(x:0,y:1)]
}
func endPoints() -> [CGPoint] {
return [CGPoint(x:1,y:1),CGPoint(x:0,y:1),CGPoint.zero,CGPoint(x:1,y:0)]
}
func midColorWithFromColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor {
var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)
var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)
let oneR = fromR + (toR - fromR) * progress
let oneG = fromG + (toG - fromG) * progress
let oneB = fromB + (toB - fromB) * progress
let oneAlpha = fromAlpha + (toAlpha - fromAlpha) * progress
let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
return oneColor
}
// This is what you call if you want to draw a full circle.
func animateCircle(duration: TimeInterval) {
animateCircleTo(duration: duration, fromValue: 0.010, toValue: 0.99)
}
// This is what you call to draw a partial circle.
func animateCircleTo(duration: TimeInterval, fromValue: CGFloat, toValue: CGFloat){
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.isRemovedOnCompletion = true
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0.010 (no circle) to 0.99 (full circle)
animation.fromValue = 0.010
animation.toValue = toValue
// Do an easeout. Don't know how to do a spring instead
//animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
// Set the circleLayer's strokeEnd property to 0.99 now so that it's the
// right value when the animation ends.
let circleMask = self.mask as! CAShapeLayer
circleMask.strokeEnd = toValue
// Do the actual animation
circleMask.removeAllAnimations()
circleMask.add(animation, forKey: "animateCircle")
}
}
And this is a little example, how to use in Swift 3.x:
let gradientRingLayer = WCGraintCircleLayer(bounds: CGRect(origin: CGPoint.zero,size:CGSize(width: 150, height: 150)), position:CGPoint(x: 200, y: 300),fromColor:UIColor.blue, toColor:UIColor.white, linewidth:4.0, toValue:0)
self.view.layer.addSublayer(gradientRingLayer)
let duration = 3.0
gradientRingLayer.animateCircleTo(duration: duration, fromValue: 0, toValue: 0.99)
This is the code in Swift 2.x:
import UIKit
class WCGraintCircleLayer: CALayer {
override init () {
super.init()
}
convenience init(bounds:CGRect,position:CGPoint,fromColor:UIColor,toColor:UIColor,linewidth:CGFloat,toValue:CGFloat) {
self.init()
self.bounds = bounds
self.position = position
let colors : [UIColor] = self.graintFromColor(fromColor, toColor:toColor, count:4)
for i in 0..<colors.count-1 {
let graint = CAGradientLayer()
graint.bounds = CGRectMake(0,0,CGRectGetWidth(bounds)/2,CGRectGetHeight(bounds)/2)
let valuePoint = self.positionArrayWithMainBounds(self.bounds)[i]
graint.position = valuePoint
print("iesimo graint position: \(graint.position)")
let fromColor = colors[i]
let toColor = colors[i+1]
let colors : [CGColorRef] = [fromColor.CGColor,toColor.CGColor]
let stopOne: CGFloat = 0.0
let stopTwo: CGFloat = 1.0
let locations : [CGFloat] = [stopOne,stopTwo]
graint.colors = colors
graint.locations = locations
graint.startPoint = self.startPoints()[i]
graint.endPoint = self.endPoints()[i]
self.addSublayer(graint)
//Set mask
let shapelayer = CAShapeLayer()
let rect = CGRectMake(0,0,CGRectGetWidth(self.bounds) - 2 * linewidth, CGRectGetHeight(self.bounds) - 2 * linewidth)
shapelayer.bounds = rect
shapelayer.position = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2)
shapelayer.strokeColor = UIColor.blueColor().CGColor
shapelayer.fillColor = UIColor.clearColor().CGColor
shapelayer.path = UIBezierPath(roundedRect: rect, cornerRadius: CGRectGetWidth(rect)/2).CGPath
shapelayer.lineWidth = linewidth
shapelayer.lineCap = kCALineCapRound
shapelayer.strokeStart = 0.010
let finalValue = (toValue*0.99)
shapelayer.strokeEnd = finalValue//0.99;
self.mask = shapelayer
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layerWithWithBounds(bounds:CGRect, position:CGPoint, fromColor:UIColor, toColor:UIColor, linewidth : CGFloat,toValue:CGFloat) -> WCGraintCircleLayer {
let layer = WCGraintCircleLayer(bounds: bounds,position: position,fromColor:fromColor, toColor: toColor,linewidth: linewidth,toValue:toValue )
return layer
}
func graintFromColor(fromColor:UIColor, toColor:UIColor, count:Int) -> [UIColor]{
var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)
var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)
var result : [UIColor]! = [UIColor]()
for i in 0...count {
let oneR:CGFloat = fromR + (toR - fromR)/CGFloat(count) * CGFloat(i)
let oneG : CGFloat = fromG + (toG - fromG)/CGFloat(count) * CGFloat(i)
let oneB : CGFloat = fromB + (toB - fromB)/CGFloat(count) * CGFloat(i)
let oneAlpha : CGFloat = fromAlpha + (toAlpha - fromAlpha)/CGFloat(count) * CGFloat(i)
let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
result.append(oneColor)
print(oneColor)
}
return result
}
func positionArrayWithMainBounds(bounds:CGRect) -> [CGPoint]{
let first = CGPointMake((CGRectGetWidth(bounds)/4)*3, (CGRectGetHeight(bounds)/4)*1)
let second = CGPointMake((CGRectGetWidth(bounds)/4)*3, (CGRectGetHeight(bounds)/4)*3)
let third = CGPointMake((CGRectGetWidth(bounds)/4)*1, (CGRectGetHeight(bounds)/4)*3)
let fourth = CGPointMake((CGRectGetWidth(bounds)/4)*1, (CGRectGetHeight(bounds)/4)*1)
print([first,second,third,fourth])
return [first,second,third,fourth]
}
func startPoints() -> [CGPoint] {
return [CGPointMake(0,0),CGPointMake(1,0),CGPointMake(1,1),CGPointMake(0,1)]
}
func endPoints() -> [CGPoint] {
return [CGPointMake(1,1),CGPointMake(0,1),CGPointMake(0,0),CGPointMake(1,0)]
}
func midColorWithFromColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor {
var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)
var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)
let oneR = fromR + (toR - fromR) * progress
let oneG = fromG + (toG - fromG) * progress
let oneB = fromB + (toB - fromB) * progress
let oneAlpha = fromAlpha + (toAlpha - fromAlpha) * progress
let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
return oneColor
}
// This is what you call if you want to draw a full circle.
func animateCircle(duration: NSTimeInterval) {
animateCircleTo(duration, fromValue: 0.010, toValue: 0.99)
}
// This is what you call to draw a partial circle.
func animateCircleTo(duration: NSTimeInterval, fromValue: CGFloat, toValue: CGFloat){
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.removedOnCompletion = true
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0.010 (no circle) to 0.99 (full circle)
animation.fromValue = 0.010
animation.toValue = toValue
// Do an easeout. Don't know how to do a spring instead
//animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
// Set the circleLayer's strokeEnd property to 0.99 now so that it's the
// right value when the animation ends.
let circleMask = self.mask as! CAShapeLayer
circleMask.strokeEnd = toValue
// Do the actual animation
circleMask.removeAllAnimations()
circleMask.addAnimation(animation, forKey: "animateCircle")
}
}
And this is a little example, how to use in Swift 2.x:
let gradientRingLayer = WCGraintCircleLayer(bounds: CGRectMake(0, 0, 150, 150), position:CGPointMake(200,300) ,fromColor:UIColor.blueColor(), toColor:UIColor.whiteColor(),linewidth:4.0, toValue:0)
self.view.layer.addSublayer(gradientRingLayer)
let duration = 3.0
gradientRingLayer.animateCircleTo(duration, fromValue: 0, toValue: 0.99)
This is rapid copy/paste remote pastebin code
Available also with animation:
I happen to visit this question,and want to post my answer.
The only thing,you need to do is use an CAShapeLayer to set Mask
[graintLayer setMask:shapeLayer]
I wrote an simple library about how to build Circle Graint Layer
This library is here
Here is the screenshot
If your backgroundLayer should just act as an mask for the gradient layer, use
gradient.mask = backgroundLayer
and add the gradient to the view's layer
backgroundView.layer.addSublayer(gradient)
otherwise create a new CAShapeLayer to act as maskLayer for the gradient
Here is Leo's library converted to Swift 3. Thanks to Alessandro Ornano's Swift 2.3 translation.
Code:
import UIKit
class WCGraintCircleLayer: CALayer {
override init () {
super.init()
}
convenience init(bounds:CGRect,position:CGPoint,fromColor:UIColor,toColor:UIColor,linewidth:CGFloat,toValue:CGFloat) {
self.init()
self.bounds = bounds
self.position = position
let colors : [UIColor] = self.graintFromColor(fromColor: fromColor, toColor:toColor, count:4)
for i in 0..<colors.count-1 {
let graint = CAGradientLayer()
graint.bounds = CGRect(x: 0, y: 0, width: bounds.width/2, height: bounds.height/2)
let valuePoint = self.positionArrayWithMainBounds(bounds: self.bounds)[i]
graint.position = valuePoint
print("iesimo graint position: \(graint.position)")
let fromColor = colors[i]
let toColor = colors[i+1]
let colors : [CGColor] = [fromColor.cgColor,toColor.cgColor]
let stopOne: CGFloat = 0.0
let stopTwo: CGFloat = 1.0
let locations : [CGFloat] = [stopOne,stopTwo]
graint.colors = colors
graint.locations = locations as [NSNumber]?
graint.startPoint = self.startPoints()[i]
graint.endPoint = self.endPoints()[i]
self.addSublayer(graint)
//Set mask
let shapelayer = CAShapeLayer()
let rect = CGRect(x: 0, y: 0, width: bounds.width - 2 * linewidth, height: bounds.height - 2 * linewidth)
shapelayer.bounds = rect
shapelayer.position = CGPoint(x: bounds.width/2, y: bounds.height/2)
shapelayer.strokeColor = UIColor.blue.cgColor
shapelayer.fillColor = UIColor.clear.cgColor
shapelayer.path = UIBezierPath(roundedRect: rect, cornerRadius: rect.width/2).cgPath
shapelayer.lineWidth = linewidth
shapelayer.lineCap = kCALineCapRound
shapelayer.strokeStart = 0.010
let finalValue = (toValue*0.99)
shapelayer.strokeEnd = finalValue//0.99;
self.mask = shapelayer
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layerWithWithBounds(bounds:CGRect, position:CGPoint, fromColor:UIColor, toColor:UIColor, linewidth : CGFloat,toValue:CGFloat) -> WCGraintCircleLayer {
let layer = WCGraintCircleLayer(bounds: bounds,position: position,fromColor:fromColor, toColor: toColor,linewidth: linewidth,toValue:toValue )
return layer
}
func graintFromColor(fromColor:UIColor, toColor:UIColor, count:Int) -> [UIColor]{
var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)
var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)
var result : [UIColor]! = [UIColor]()
for i in 0...count {
let oneR:CGFloat = fromR + (toR - fromR)/CGFloat(count) * CGFloat(i)
let oneG : CGFloat = fromG + (toG - fromG)/CGFloat(count) * CGFloat(i)
let oneB : CGFloat = fromB + (toB - fromB)/CGFloat(count) * CGFloat(i)
let oneAlpha : CGFloat = fromAlpha + (toAlpha - fromAlpha)/CGFloat(count) * CGFloat(i)
let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
result.append(oneColor)
print(oneColor)
}
return result
}
func positionArrayWithMainBounds(bounds:CGRect) -> [CGPoint]{
let first = CGPoint(x: (bounds.width/4)*3, y: (bounds.height/4)*1)
let second = CGPoint(x: (bounds.width/4)*3, y: (bounds.height/4)*3)
let third = CGPoint(x: (bounds.width/4)*1, y: (bounds.height/4)*3)
let fourth = CGPoint(x: (bounds.width/4)*1, y: (bounds.height/4)*1)
print([first,second,third,fourth])
return [first,second,third,fourth]
}
func startPoints() -> [CGPoint] {
return [CGPoint(x: 0, y: 0),CGPoint(x: 1, y: 0),CGPoint(x: 1, y: 1),CGPoint(x: 0, y: 1)]
}
func endPoints() -> [CGPoint] {
return [CGPoint(x: 1, y: 1),CGPoint(x: 0, y: 1),CGPoint(x: 0, y: 0),CGPoint(x: 1, y: 0)]
}
func midColorWithFromColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor {
var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0
fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha)
var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0
toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha)
let oneR = fromR + (toR - fromR) * progress
let oneG = fromG + (toG - fromG) * progress
let oneB = fromB + (toB - fromB) * progress
let oneAlpha = fromAlpha + (toAlpha - fromAlpha) * progress
let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha)
return oneColor
}
// This is what you call if you want to draw a full circle.
func animateCircle(duration: TimeInterval) {
animateCircleTo(duration: duration, fromValue: 0.010, toValue: 0.99)
}
// This is what you call to draw a partial circle.
func animateCircleTo(duration: TimeInterval, fromValue: CGFloat, toValue: CGFloat){
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.isRemovedOnCompletion = true
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0.010 (no circle) to 0.99 (full circle)
animation.fromValue = 0.010
animation.toValue = toValue
// Do an easeout. Don't know how to do a spring instead
//animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
// Set the circleLayer's strokeEnd property to 0.99 now so that it's the
// right value when the animation ends.
let circleMask = self.mask as! CAShapeLayer
circleMask.strokeEnd = toValue
// Do the actual animation
circleMask.removeAllAnimations()
circleMask.add(animation, forKey: "animateCircle")
}
}
How to use:
let gradientRingLayer = WCGraintCircleLayer(bounds: CGRect(x: 0, y: 0, width: 150, height: 150), position:CGPoint(x: 200, y: 300),fromColor:UIColor.blue, toColor:UIColor.white, linewidth:4.0, toValue:0)
self.view.layer.addSublayer(gradientRingLayer)
let duration = 3.0
gradientRingLayer.animateCircleTo(duration: duration, fromValue: 0, toValue: 0.99)
I found a simpler answer without using mulitple layers, blending multiple layers may impact animation performance, generally should be avoided as much as possible.
override func draw(_ rect: CGRect) {
let lineWidth: CGFloat = CGFloat(2)
let strokeColor: UIColor = UIColor.black
let startAngle: CGFloat = 0
let maxAngle: CGFloat = CGFloat(Double.pi * 2)
let lineCapStyle: CGLineCap = .round
let gradations = 255
let center = CGPoint(x: bounds.origin.x + bounds.size.width / 2, y: bounds.origin.y + bounds.size.height / 2)
let radius = (min(bounds.size.width, bounds.size.height) - lineWidth) / 2
for i in 1 ... gradations {
let percent0 = CGFloat(i - 1) / CGFloat(gradations)
let percent1 = CGFloat(i) / CGFloat(gradations)
let angle0 = startAngle + (maxAngle - startAngle) * percent0
let angle1 = startAngle + (maxAngle - startAngle) * percent1
let context = UIGraphicsGetCurrentContext()!
context.setLineWidth(lineWidth)
context.setLineCap(lineCapStyle)
let path = CGMutablePath()
path.addArc(center: center, radius: radius + lineWidth / 2, startAngle: angle0, endAngle: angle1, clockwise: true)
path.addArc(center: center, radius: radius - lineWidth / 2, startAngle: angle1, endAngle: angle0, clockwise: false)
path.closeSubpath()
let colors = [strokeColor.withAlphaComponent(percent0).cgColor, strokeColor.withAlphaComponent(percent1).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let colorLocations: [CGFloat] = [0.0, 1.0]
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: colorLocations)!
let startPoint = CGPoint.init(x: center.x + cos(angle0) * radius, y: center.y + sin(angle0) * radius)
let endPoint = CGPoint.init(x: center.x + cos(angle1) * radius, y: center.y + sin(angle1) * radius)
context.saveGState()
context.addPath(path)
context.clip()
context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: [])
context.restoreGState()
}
}
As of iOS 12, you can use kCAGradientLayerConic as the type for your gradient layer:
// Conical gradient layer
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.clockView.bounds;
gradientLayer.type = kCAGradientLayerConic;
gradientLayer.startPoint = CGPointMake(0.5, 0.5);
gradientLayer.endPoint = CGPointMake(1, 1.0);
gradientLayer.colors = #[(__bridge id)[UIColor colorWithRed:1.0/255.0 green:25.0/255.0 blue:147.0/255.0 alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:250.0/255.0 green:226.0/255.0 blue:50.0/255.0 alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:250.0/255.0 green:226.0/255.0 blue:50.0/255.0 alpha:1.0].CGColor,
(__bridge id)[UIColor colorWithRed:1.0/255.0 green:25.0/255.0 blue:147.0/255.0 alpha:1.0].CGColor];
gradientLayer.locations = #[[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:0.4], [NSNumber numberWithFloat:0.5], [NSNumber numberWithFloat:0.6], [NSNumber numberWithFloat:1.0f]];
// Circular mask for gradient layer
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = gradientLayer.bounds;
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath, NULL, CGRectInset(self.view.layer.bounds, 10.0, 10.0));
maskLayer.path = circlePath;
gradientLayer.mask = maskLayer;
[self.view.layer addSublayer:gradientLayer];
CGPathRelease(circlePath);
// Place a second layer over the gradient layer...
CALayer *circleLayer = [CALayer layer];
circleLayer.frame = gradientLayer.bounds;
[circleLayer setBackgroundColor:[UIColor darkGrayColor].CGColor];
// ...and mask it with a smaller circle
CAShapeLayer *maskLayer2 = [CAShapeLayer layer];
maskLayer2.frame = circleLayer.bounds;
CGMutablePathRef circlePath2 = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath2, NULL, CGRectInset(self.view.layer.bounds, 60.0, 60.0));
maskLayer2.path = circlePath2;
CGPathRelease(circlePath2);
circleLayer.mask = maskLayer2;
[self.view.layer insertSublayer:circleLayer atIndex:1];