CAShapeLayer rotate around the center - swift

The layer has a shape of ring. I want it to rotate around its center. Here's my code:
class ClipRotateView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func animate() {
let ring = CAShapeLayer()
let path = UIBezierPath()
path.addArcWithCenter(CGPoint(x: frame.width/2, y: frame.height/2), radius: frame.width/2, startAngle: CGFloat(-3 * M_PI_4), endAngle: CGFloat(-M_PI_4), clockwise: false)
ring.path = path.CGPath
ring.fillColor = nil
ring.strokeColor = UIColor.whiteColor().CGColor
ring.lineWidth = 2
ring.anchorPoint = CGPoint(x: 0.5, y: 0.5)
layer.addSublayer(ring)
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation.keyTimes = [0, 0.5, 1]
scaleAnimation.values = [1, 0.6, 1]
let rotateAnimation = CAKeyframeAnimation(keyPath: "transform.rotation.z")
rotateAnimation.keyTimes = scaleAnimation.keyTimes
rotateAnimation.values = [0, M_PI, 2 * M_PI]
let animation = CAAnimationGroup()
animation.animations = [scaleAnimation, rotateAnimation]
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = 0.75
animation.repeatCount = HUGE
animation.removedOnCompletion = false
ring.addAnimation(animation, forKey: nil)
layer.borderWidth=1
layer.borderColor = UIColor.blackColor().CGColor
}
}
Even though I've set the anchorPoint to (0.5, 0.5), the ring is still rotating around the left top corner.

try
ring.frame = frame
make sure the center of the frame is intended rotation center
works for me.

Related

Change color gradient around donut view

I am trying to make an animated donut view that when given a value between 0 and 100 it will animate round the view up to that number. I have this working fine but want to fade the color from one to another, then another on the way around. Currently, when I add my gradient it goes from left to right and not around the circumference of the donut view.
class CircleScoreView: UIView {
private let outerCircleLayer = CAShapeLayer()
private let outerCircleGradientLayer = CAGradientLayer()
private let outerCircleLineWidth: CGFloat = 5
override init(frame: CGRect) {
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
buildLayers()
}
/// Value must be within 0...100 range
func setScore(_ value: Int, animated: Bool = false) {
if value != 0 {
let clampedValue: CGFloat = CGFloat(value.clamped(to: 0...100)) / 100
if !animated {
outerCircleLayer.strokeEnd = clampedValue
} else {
let outerCircleAnimation = CABasicAnimation(keyPath: "strokeEnd")
outerCircleAnimation.duration = 1.0
outerCircleAnimation.fromValue = 0
outerCircleAnimation.toValue = clampedValue
outerCircleAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
outerCircleLayer.strokeEnd = clampedValue
outerCircleLayer.add(outerCircleAnimation, forKey: "outerCircleAnimation")
}
outerCircleGradientLayer.colors = [Constant.Palette.CircleScoreView.startValue.cgColor,
Constant.Palette.CircleScoreView.middleValue.cgColor,
Constant.Palette.CircleScoreView.endValue.cgColor]
}
}
private func buildLayers() {
// Outer background circle
let arcCenter = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
let startAngle = CGFloat(-0.5 * Double.pi)
let endAngle = CGFloat(1.5 * Double.pi)
let circlePath = UIBezierPath(arcCenter: arcCenter,
radius: (frame.size.width - outerCircleLineWidth) / 2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
// Outer circle
setupOuterCircle(outerCirclePath: circlePath)
}
private func setupOuterCircle(outerCirclePath: UIBezierPath) {
outerCircleLayer.path = outerCirclePath.cgPath
outerCircleLayer.fillColor = UIColor.clear.cgColor
outerCircleLayer.strokeColor = UIColor.black.cgColor
outerCircleLayer.lineWidth = outerCircleLineWidth
outerCircleLayer.lineCap = CAShapeLayerLineCap.round
outerCircleGradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
outerCircleGradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
outerCircleGradientLayer.frame = bounds
outerCircleGradientLayer.mask = outerCircleLayer
layer.addSublayer(outerCircleGradientLayer)
}
}
I am going for something like this but the color isn't one block but gradients around the donut view from one color to the next.
If you imported AngleGradientLayer into your project then all you should need to do is change:
private let outerCircleGradientLayer = CAGradientLayer() to
private let outerCircleGradientLayer = AngleGradientLayer()

Animating a UIView's mask (larger circle CAShapeLayer to smaller)

I have a UIView class that I add to my main view that blocks everything out except for a circle shape. Now I'm trying to animate the circle. I decided to use CABasicAnimation to do this, but the circle stays large and doesn't animate. I'm not sure if I'm missing something or what I'm trying to do is impossible.
class SpotlightOverlay: UIView {
let overlayView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
init(frame: CGRect,
xOffset: CGFloat,
yOffset: CGFloat,
radius: CGFloat) {
super.init(frame: frame)
overlayView.frame = frame
overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
let path = CGMutablePath()
path.addArc(center: CGPoint(x: xOffset, y: yOffset),
radius: radius*5,
startAngle: 0.0,
endAngle: 2.0 * .pi,
clockwise: false)
path.addRect(CGRect(origin: .zero, size: overlayView.frame.size))
let path2 = CGMutablePath()
path2.addArc(center: CGPoint(x: xOffset, y: yOffset),
radius: radius,
startAngle: 0.0,
endAngle: 2.0 * .pi,
clockwise: false)
path2.addRect(CGRect(origin: .zero, size: overlayView.frame.size))
let anim = CABasicAnimation(keyPath: "path")
anim.fromValue = path
anim.toValue = path2
anim.duration = 6.0
anim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
let maskLayer = CAShapeLayer()
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.add(anim, forKey: nil)
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.path = path
CATransaction.commit()
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
overlayView.layer.mask = maskLayer
overlayView.clipsToBounds = true
self.addSubview(overlayView)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Instead of using a CGMutablePath, I achieved what I wanted with a UIBezierPath. More specifically, it appears as a hole in the view that can animate.
import UIKit
class SpotlightOverlay: UIView {
let overlayView = UIView()
let maskLayer = CAShapeLayer()
var initXOffset:CGFloat = 0
var initYOffset:CGFloat = 0
var initDiameter:CGFloat = 0
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
init(frame: CGRect,
xOffset: CGFloat,
yOffset: CGFloat,
startDiameter: CGFloat,
endDiameter:CGFloat,
duration:Double) {
super.init(frame: frame)
self.isUserInteractionEnabled = false
self.overlayView.isUserInteractionEnabled = false
overlayView.frame = frame
overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
let padding:CGFloat = 50
let startDiameter = startDiameter + padding
let endDiameter = endDiameter + padding
initXOffset = xOffset
initYOffset = yOffset
initDiameter = endDiameter
let ovalFrame1 = CGRect(x:xOffset-(startDiameter/2),
y:yOffset-(startDiameter/2),
width:startDiameter,
height:startDiameter)
let ovalFrame2 = CGRect(x:xOffset-(endDiameter/2),
y:yOffset-(endDiameter/2),
width:endDiameter,
height:endDiameter)
let path = UIBezierPath(ovalIn: ovalFrame1)
let path2 = UIBezierPath(ovalIn: ovalFrame2)
maskLayer.backgroundColor = UIColor.black.cgColor
path.append(UIBezierPath(rect: self.bounds))
path2.append(UIBezierPath(rect: self.bounds))
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.path = path.cgPath
CATransaction.commit()
let anim = CABasicAnimation(keyPath: "path")
anim.fromValue = path.cgPath
anim.toValue = path2.cgPath
anim.duration = duration
anim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
anim.fillMode = CAMediaTimingFillMode.forwards
anim.isRemovedOnCompletion = false
maskLayer.add(anim, forKey: nil)
overlayView.layer.mask = maskLayer
overlayView.clipsToBounds = true
self.addSubview(overlayView)
}
func animateTo(xOffset: CGFloat,
yOffset: CGFloat,
endDiameter:CGFloat,
duration:Double) {
let padding:CGFloat = 50
let startDiameter = initDiameter
let endDiameter = endDiameter + padding
let ovalFrame1 = CGRect(x:initXOffset-(startDiameter/2),
y:initYOffset-(startDiameter/2),
width:startDiameter,
height:startDiameter)
let ovalFrame2 = CGRect(x:xOffset-(endDiameter/2),
y:yOffset-(endDiameter/2),
width:endDiameter,
height:endDiameter)
initXOffset = xOffset
initYOffset = yOffset
let path = UIBezierPath(ovalIn: ovalFrame1)
let path2 = UIBezierPath(ovalIn: ovalFrame2)
path.append(UIBezierPath(rect: self.bounds))
path2.append(UIBezierPath(rect: self.bounds))
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.path = path.cgPath
CATransaction.commit()
let anim = CABasicAnimation(keyPath: "path")
anim.fromValue = path.cgPath
anim.toValue = path2.cgPath
anim.duration = duration
anim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
anim.fillMode = CAMediaTimingFillMode.forwards
anim.isRemovedOnCompletion = false
maskLayer.add(anim, forKey: nil)
initDiameter = endDiameter
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
First, initialize the class and then call the animateTo function any time you want to animate the hole moving.

Animate drawing of full circle continuously without any lag during restart using CABasicAnimation

I am trying to have an animation in a view controller in which the circle rotates with animation. The circle should rotate until a process completed like a gif below. I have implemented the circle animation but couldn't reach to the point what I want to achieve.
import UIKit
class ViewController: UIViewController {
var circle : Circle?;
override func viewDidLoad() {
super.viewDidLoad();
view.backgroundColor = UIColor.white;
setupViews();
}
func setupViews(){
circle = Circle(frame: self.view.frame);
view.addSubview(circle!);
circle?.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true;
circle?.topAnchor.constraint(equalTo: view.topAnchor).isActive = true;
circle?.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true;
circle?.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true;
}
}
class Circle : UIView{
override init(frame: CGRect) {
super.init(frame: frame);
self.backgroundColor = .blue;
self.translatesAutoresizingMaskIntoConstraints = false;
setupCircle();
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let gradientLayer = CAGradientLayer();
func setupCircle(){
layer.addSublayer(shapeLayer);
let circlePath = UIBezierPath(arcCenter: CGPoint(x: self.frame.width / 2 - 50, y: self.frame.height / 2 - 50), radius: 50, startAngle: CGFloat(Double.pi * (0 / 4)), endAngle: CGFloat(Double.pi * 2), clockwise: true);
shapeLayer.path = circlePath.cgPath;
let group = CAAnimationGroup()
group.animations = [animateStrokeEnd, animateOpacity]
group.duration = 0.8
group.repeatCount = HUGE // repeat forver
shapeLayer.add(group, forKey: nil)
}
let shapeLayer: CAShapeLayer = {
let layer = CAShapeLayer();
layer.strokeColor = UIColor.white.cgColor;
layer.lineWidth = 5;
layer.fillColor = UIColor.clear.cgColor;
layer.strokeStart = 0
layer.strokeEnd = 1;
return layer;
}();
let animateOpacity : CABasicAnimation = {
let animation = CABasicAnimation(keyPath: "opacity");
animation.fromValue = 0;
animation.toValue = 0.8;
animation.byValue = 0.01;
animation.repeatCount = Float.infinity;
return animation
}();
let animateStrokeEnd: CABasicAnimation = {
let animation = CABasicAnimation(keyPath: "strokeEnd");
animation.fromValue = 0;
animation.repeatCount = Float.infinity;
animation.toValue = 1;
return animation;
}();
}
I am using strokeEnd animation to implement the animation. And opacity to animate the color. But when the circle reaches 360 degree, its makes a lag before starting a new circle.
Does anybody know how to remove this effect and get smooth animation?
The above code produces this animation
But i want to achieve this animation
Also the stroke colour is different from the original animation. Can we achieve this animation using the CABasicAnimation?
Rather than trying to animate the actual drawing, just draw the view once and then animate it.
Here is a custom PadlockView and a custom CircleView which mimic the animation you showed. To use it, add the code below to your project. Add a UIView to your Storyboard, change its class to PadlockView, and make an #IBOutlet to it (called padlock perhaps). When you want the view to animate, set padlock.circle.isAnimating = true. To stop animating, set padlock.circle.isAnimating = false.
CircleView.swift
// This UIView extension was borrowed from #keval's answer:
// https://stackoverflow.com/a/41160100/1630618
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 3) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat.pi * 2
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = duration
rotateAnimation.repeatCount = Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
}
class CircleView: UIView {
var foregroundColor = UIColor.white
var lineWidth: CGFloat = 3.0
var isAnimating = false {
didSet {
if isAnimating {
self.isHidden = false
self.rotate360Degrees(duration: 1.0)
} else {
self.isHidden = true
self.layer.removeAllAnimations()
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.isHidden = true
self.backgroundColor = .clear
}
override func draw(_ rect: CGRect) {
let width = bounds.width
let height = bounds.height
let radius = (min(width, height) - lineWidth) / 2.0
var currentPoint = CGPoint(x: width / 2.0 + radius, y: height / 2.0)
var priorAngle = CGFloat(360)
for angle in stride(from: CGFloat(360), through: 0, by: -2) {
let path = UIBezierPath()
path.lineWidth = lineWidth
path.move(to: currentPoint)
currentPoint = CGPoint(x: width / 2.0 + cos(angle * .pi / 180.0) * radius, y: height / 2.0 + sin(angle * .pi / 180.0) * radius)
path.addArc(withCenter: CGPoint(x: width / 2.0, y: height / 2.0), radius: radius, startAngle: priorAngle * .pi / 180.0 , endAngle: angle * .pi / 180.0, clockwise: false)
priorAngle = angle
foregroundColor.withAlphaComponent(angle/360.0).setStroke()
path.stroke()
}
}
}
PadlockView.swift
class PadlockView: UIView {
var circle: CircleView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.backgroundColor = .clear
circle = CircleView()
circle.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(circle)
circle.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
circle.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
circle.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
circle.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
}
override func draw(_ rect: CGRect) {
let width = bounds.width
let height = bounds.height
let lockwidth = width / 3
let lockheight = height / 4
let boltwidth = lockwidth * 2 / 3
UIColor.white.setStroke()
let path = UIBezierPath()
path.move(to: CGPoint(x: (width - lockwidth) / 2, y: height / 2))
path.addLine(to: CGPoint(x: (width + lockwidth) / 2, y: height / 2))
path.addLine(to: CGPoint(x: (width + lockwidth) / 2, y: height / 2 + lockheight))
path.addLine(to: CGPoint(x: (width - lockwidth) / 2, y: height / 2 + lockheight))
path.close()
path.move(to: CGPoint(x: (width - boltwidth) / 2, y: height / 2))
path.addLine(to: CGPoint(x: (width - boltwidth) / 2, y: height / 2 - boltwidth / 4))
path.addArc(withCenter: CGPoint(x: width/2, y: height / 2 - boltwidth / 4), radius: boltwidth / 2, startAngle: .pi, endAngle: 0, clockwise: true)
path.lineWidth = 2.0
path.stroke()
}
}
Note: Continuous animation code courtesy of this answer.
Here is a demo that I setup with the following code in my ViewController:
#IBOutlet weak var padlock: PadlockView!
#IBAction func startStop(_ sender: UIButton) {
if sender.currentTitle == "Start" {
sender.setTitle("Stop", for: .normal)
padlock.circle.isAnimating = true
} else {
sender.setTitle("Start", for: .normal)
padlock.circle.isAnimating = false
}
}

Draw only half of circle

I want to draw only the left or right half of the circle.
I can draw the circle from 0 to 0.5 (right side) with no problems. But drawing the circle from 0.5 to 1 (left side) doesn't work.
call:
addCircleView(circle, isForeground: false, duration: 0.0, fromValue: 0.5, toValue: 1.0)
This is my code:
func addCircleView( myView : UIView, isForeground : Bool, duration : NSTimeInterval, fromValue: CGFloat, toValue : CGFloat ) -> CircleView {
let circleWidth = CGFloat(280)
let circleHeight = circleWidth
// Create a new CircleView
let circleView = CircleView(frame: CGRectMake(0, 0, circleWidth, circleHeight))
//Setting the color.
if (isForeground == true) {
circleView.setStrokeColor((UIColor(hexString: "#ffefdb")?.CGColor)!)
} else {
// circleLayer.strokeColor = UIColor.grayColor().CGColor
//Chose to use hexes because it's much easier.
circleView.setStrokeColor((UIColor(hexString: "#51acbc")?.CGColor)!)
}
myView.addSubview(circleView)
//Rotate the circle so it starts from the top.
circleView.transform = CGAffineTransformMakeRotation(-1.56)
// Animate the drawing of the circle
circleView.animateCircleTo(duration, fromValue: fromValue, toValue: toValue)
return circleView
}
Circle view class
import UIKit
extension UIColor {
/// UIColor(hexString: "#cc0000")
internal convenience init?(hexString:String) {
guard hexString.characters[hexString.startIndex] == Character("#") else {
return nil
}
guard hexString.characters.count == "#000000".characters.count else {
return nil
}
let digits = hexString.substringFromIndex(hexString.startIndex.advancedBy(1))
guard Int(digits,radix:16) != nil else{
return nil
}
let red = digits.substringToIndex(digits.startIndex.advancedBy(2))
let green = digits.substringWithRange(Range<String.Index>(digits.startIndex.advancedBy(2)..<digits.startIndex.advancedBy(4)))
let blue = digits.substringWithRange(Range<String.Index>(digits.startIndex.advancedBy(4)..<digits.startIndex.advancedBy(6)))
let redf = CGFloat(Double(Int(red, radix:16)!) / 255.0)
let greenf = CGFloat(Double(Int(green, radix:16)!) / 255.0)
let bluef = CGFloat(Double(Int(blue, radix:16)!) / 255.0)
self.init(red: redf, green: greenf, blue: bluef, alpha: CGFloat(1.0))
}
}
class CircleView: UIView {
var circleLayer: CAShapeLayer!
var from : CGFloat = 0.0;
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = UIColor.clearColor().CGColor
//I'm going to change this in the ViewController that uses this. Not the best way, I know but alas.
circleLayer.strokeColor = UIColor.redColor().CGColor
//You probably want to change this width
circleLayer.lineWidth = 3.0;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
layer.addSublayer(circleLayer)
}
func setStrokeColor(color : CGColorRef) {
circleLayer.strokeColor = color
}
// This is what you call if you want to draw a full circle.
func animateCircle(duration: NSTimeInterval) {
animateCircleTo(duration, fromValue: 0.0, toValue: 1.0)
}
// 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")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = toValue
// Do an easeout. Don't know how to do a spring instead
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = toValue
// Do the actual animation
circleLayer.addAnimation(animation, forKey: "animateCircle")
}
// required function
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You need to set:
circleLayer.strokeStart = fromValue
in the animateCircleTo(duration...) function.
You set the end of the stroke, but not the beginning. Consequently, all circle animations begin from 0.0, even if you intend them to begin at a later part of the stroke.
Like this:
// 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")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = toValue
// Do an easeout. Don't know how to do a spring instead
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeStart = fromValue
circleLayer.strokeEnd = toValue
// Do the actual animation
circleLayer.addAnimation(animation, forKey: "animateCircle")
}
The easiest way is to mask or clip out the half of the circle you don't want.
class HalfCircleView : UIView {
override func draw(_ rect: CGRect) {
let p = UIBezierPath(rect: CGRect(x: 50, y: 0, width: 50, height: 100))
p.addClip()
let p2 = UIBezierPath(ovalIn: CGRect(x: 10, y: 10, width: 80, height: 80))
p2.lineWidth = 2
p2.stroke()
}
}
Of course, where you put the clipping is where the circle half will appear. And once you have the half-circle view of course you can rotate it etc.
In swift ui. Draws a clock wise arc, with a progress:
This is the implementation:
struct ArcShape: Shape {
var width: CGFloat
var height: CGFloat
var progress: Double
func path(in rect: CGRect) -> Path {
let bezierPath = UIBezierPath()
let endAngle = 360.0 * progress - 90.0
bezierPath.addArc(withCenter: CGPoint(x: width / 2, y: height / 2),
radius: width / 3,
startAngle: CGFloat(-90 * Double.pi / 180),
endAngle: CGFloat(endAngle * Double.pi / 180),
clockwise: true)
return Path(bezierPath.cgPath)
}
}
Usage:
ArcShape(width: geometry.size.width, height: geometry.size.height, progress: 0.75)
.stroke(lineWidth: 5)
.fill(Color(R.color.colorBlue.name))
.frame(width: geometry.size.width , height: geometry.size.height)

Swift: How to fill in CAShapeLayer circle with color from outside going in?

Alright, Im pretty new to CAShapeLayer but I need to know how to fill in a CAShapeLayer circle with a color starting from the outside outline and going to the center like this -
I am drawing my circle with this modified from a tutorial:
var circleLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = UIColor.clearColor().CGColor
circleLayer.strokeColor = UIColor.whiteColor().CGColor
circleLayer.lineWidth = 1.5;
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
layer.addSublayer(circleLayer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateCircle(duration: NSTimeInterval) {
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
// Set the animation duration appropriately
animation.duration = duration
// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = 1
// Do a linear animation (i.e. the speed of the animation stays the same)
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = 1.0
// Do the actual animation
circleLayer.addAnimation(animation, forKey: "animateCircle")
}
}
Then I add the CircleView like this -
func addCircleView() {
let diceRoll = CGFloat(Int(arc4random_uniform(7))*50)
var circleWidth = screenSize.width*(85/330)
var circleHeight = circleWidth
// Create a new CircleView
circleView = CircleView(frame: CGRectMake(diceRoll, 0, circleWidth, circleHeight))
circleView.center = CGPointMake(screenSize.width*0.5, screenSize.height*0.71)
view.addSubview(circleView)