IOS - circle gradient

I am trying to make a circular 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 is not affected by:

 backgroundLayer.path = CGPathCreateWithEllipseInRect(backgroundLayer.frame, nil) 

And still has its original form. Is there a way to mask the gradient with an elliptical layer without using CGContext * instructions?

Thanks,

MG

+8
ios swift
source share
4 answers

I translated the Leo library into Swift 3.x , in other words, a good WCGradientCircleLayer is called in objective-C (it works just like the aspect)

 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 strokeEnd property to 0.99 now so that it 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 small example of 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 strokeEnd property to 0.99 now so that it 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 small example: how to use it 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 a quick copy / paste of remote pastebin code

static image

Also available with animation :

dynamic image

+9
source share

I was fortunate enough to visit this question and I want to post my answer.

The only thing you need to do is use CAShapeLayer to set the mask

 [graintLayer setMask:shapeLayer] 

I wrote a simple library on how to build a Circle Graint Layer

This library is here

Here is a screenshot screenshots.png

+4
source share

If your background layer should just act as a mask for the gradient layer, use

 gradient.mask = backgroundLayer 

and add a gradient to the view layer

 backgroundView.layer.addSublayer(gradient) 

otherwise create a new CAShapeLayer to act like a maskLayer for the gradient

+3
source share

Here is the Leo library converted to Swift 3. Thanks to the translation of Alessandro Ornano Swift 2.3.

the 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 strokeEnd property to 0.99 now so that it 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) 
0
source share

All Articles