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
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'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 you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With