Unable to get correct center for CAShapeLayer - ios

Can someone please point out what am i doing incorrectly? I need to adapt to all iphone screens in portrait. is this a good approach?
var progressCircle = CAShapeLayer()
let centerPoint = CGPoint (x: vu_SubmissionViewWithImageInCenter.bounds.width / 2, y: imgVu_Submission.bounds.width / 2)
let circleRadius : CGFloat = vu_SubmissionViewWithImageInCenter.bounds.width / 2 * 0.94
let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true )
progressCircle = CAShapeLayer ()
progressCircle.path = circlePath.cgPath
progressCircle.strokeColor = UIColor.fuschia.cgColor
progressCircle.fillColor = UIColor.clear.cgColor
progressCircle.lineWidth = 3.5
progressCircle.strokeStart = 0
progressCircle.strokeEnd = 1.0
vu_SubmissionViewWithImageInCenter.layer.addSublayer(progressCircle)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1.0
animation.duration = 5.0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
progressCircle.add(animation, forKey: "ani")
animation.delegate = self
5s

Your radius calculation is wrong. Have a distance calculation from arc centre to the imageview edges and use that as your radius.

Related

animate CAShapeLayer on circular path

I want to animate my thumb layer on the drawn path. I can animate the progress layer as per the value but, moving the entire layer doesn't work.
func setProgress(_ value:Double, _ animated :Bool) {
let progressAnimation = CABasicAnimation(keyPath: "strokeEnd")
progressAnimation.duration = animated ? 0.6 : 0.0
progressAnimation.fromValue = currentValue
progressAnimation.toValue = value
progressAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
progressLayer.strokeEnd = CGFloat(value)
progressLayer.add(progressAnimation, forKey: "animateprogress")
}
Using this function I can animate the progress layer. and I'm stuck on animating thumb position.
I tried some things but, it's not working.
failed attempt 1
guard let path = thumbLayer.path else {return}
let center = CGPoint(x: frame.width/2, y: frame.height/2)
let radius = min(frame.width, frame.height)/2 - max(trackWidth, max(progressWidth, thumbWidth))/2
let thumbAngle = 2 * .pi * currentValue - (.pi/2)
let thumbX = CGFloat(cos(thumbAngle)) * radius
let thumbY = CGFloat(sin(thumbAngle)) * radius
let newThumbCenter = CGPoint(x: center.x + thumbX, y: center.y + thumbY)
let thumbPath = UIBezierPath(arcCenter: newThumbCenter, radius: thumbWidth/2, startAngle: -.pi/2, endAngle: .pi*1.5, clockwise: true)
let thumbAnimation = CABasicAnimation(keyPath: "path")
thumbAnimation.duration = animated ? 0.6 : 0.0
thumbAnimation.fromValue = path
thumbAnimation.toValue = thumbPath.cgPath
thumbAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
thumbLayer.strokeEnd = CGFloat(value)
thumbLayer.add(thumbAnimation, forKey: "animateprogress")
failed attempt 2
let intialPosition = thumbLayer.position
let center = CGPoint(x: frame.width/2, y: frame.height/2)
let radius = min(frame.width, frame.height)/2 - max(trackWidth, max(progressWidth, thumbWidth))/2
let thumbAngle = 2 * .pi * currentValue - (.pi/2)
let thumbX = CGFloat(cos(thumbAngle)) * radius
let thumbY = CGFloat(sin(thumbAngle)) * radius
let newThumbCenter = CGPoint(x: center.x + thumbX - thumbCenter.x, y: center.y + thumbY - thumbCenter.y)
thumbLayer.position.x = newThumbCenter.x
let thumbAnimationX = CABasicAnimation(keyPath: "progress.x")
thumbAnimationX.duration = animated ? 0.6 : 0.0
thumbAnimationX.fromValue = intialPosition.x
thumbAnimationX.toValue = newThumbCenter.x
thumbAnimationX.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
thumbLayer.add(thumbAnimationX, forKey: "progress.x")
thumbLayer.position.y = newThumbCenter.y
let thumbAnimationY = CABasicAnimation(keyPath: "progress.y")
thumbAnimationY.duration = animated ? 0.6 : 0.0
thumbAnimationY.fromValue = intialPosition.y
thumbAnimationY.toValue = newThumbCenter.y
thumbAnimationY.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
thumbLayer.add(thumbAnimationY, forKey: "progress.y")
In your first attempt, you're trying to animate the thumb's path (the outline of the red circle). In your second attempt, you're trying to animate the position of thumbLayer (but you animated the wrong key paths).
Since you're having difficulty making either of those work, let's try a different approach. Take a look at the standard Swiss railway clock:
Specifically, look at the red dot at the end of the second hand. How does the clock move that red dot around the face? By rotating the second hand around the center of the face.
That's what we're going to do, but we won't draw the whole second hand. We'll just draw the red dot at the end.
We'll put the origin of the thumbLayer's coordinate system at the center of the circular track, and we'll set its path so that the thumb appears on the path. Here's the layout:
Here's code to set up a circleLayer and a thumbLayer:
private let circleLayer = CAShapeLayer()
private let thumbLayer = CAShapeLayer()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let circleRadius: CGFloat = 100
let thumbRadius: CGFloat = 22
let circleCenter = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
circleLayer.fillColor = nil
circleLayer.strokeColor = UIColor.black.cgColor
circleLayer.lineWidth = 4
circleLayer.lineJoin = .round
circleLayer.path = CGPath(ellipseIn: CGRect(x: -circleRadius, y: -circleRadius, width: 2 * circleRadius, height: 2 * circleRadius), transform: nil)
circleLayer.position = circleCenter
view.layer.addSublayer(circleLayer)
thumbLayer.fillColor = UIColor.red.cgColor
thumbLayer.strokeColor = nil
thumbLayer.path = CGPath(ellipseIn: CGRect(x: -thumbRadius, y: -circleRadius - thumbRadius, width: 2 * thumbRadius, height: 2 * thumbRadius), transform: nil)
thumbLayer.position = circleCenter
view.layer.addSublayer(thumbLayer)
}
With this layout, we can move the thumb along the track without changing its position. Instead, we rotate thumbLayer around its origin:
#IBAction func buttonWasTapped() {
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.fromValue = 0
animation.toValue = 2 * CGFloat.pi
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.duration = 1.5
animation.isAdditive = true
thumbLayer.add(animation, forKey: nil)
}
Result:

How to draw Semi(half) circle and animate it till particular angle in swift 4?

let slayer = CAShapeLayer()
let center = CGPoint(x: (bounds.width / 2) + 4, y: bounds.height - 8)
let radius: CGFloat = bounds.height - 16
let startAngle: CGFloat = 4 * .pi / 4
let endAngle: CGFloat = 0.0
slayer.path = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true).cgPath
slayer.lineWidth = 15.0
slayer.lineCap = kCALineCapRound
slayer.strokeColor = UIColor.blue.cgColor
slayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(slayer)
Please look at the following image. The requirement is this circle with animation it till a particular angle.
You can animate the circle by adding another layer on top of it and animating it's stroke end property.
func animate()
{
let slayer = CAShapeLayer()
let center = CGPoint(x: (bounds.width / 2) + 4, y: bounds.height - 8)
let radius: CGFloat = bounds.height - 16
let startAngle: CGFloat = 4 * .pi / 4
let endAngle: CGFloat = 0.0
slayer.path = UIBezierPath(arcCenter: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true).cgPath
slayer.lineWidth = 15.0
slayer.lineCap = kCALineCapRound
slayer.strokeColor = UIColor.blue.cgColor
slayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(slayer)
slayer.strokeEnd = 0.0
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 60.0 . //Customize the time of your animation here.
animation.fromValue = 0.0
animation.toValue = 1.0
animation.timingFunction = CAMediaTimingFunction(name:
kCAMediaTimingFunctionLinear)
slayer.strokeEnd = 1.0
slayer.add(animation, forKey: nil)
}
Use 2 CA Shape Layer objects, one for gray(w/o animation) & other blue(with animation).
func addCircle(color: UIColor) -> CAShapeLayer
{
let centre = CGPoint(x: view.bounds.size.width/2, y: view.bounds.size.height/2)
let beizerPath = UIBezierPath(arcCenter: centre, radius: 30.0, startAngle: .pi, endAngle: 2.0 * .pi, clockwise: true)
let circleLayer = CAShapeLayer()
circleLayer.path = beizerPath.cgPath
circleLayer.fillColor = color.cgColor
circleLayer.strokeEnd = 1.0
captureButton.layer.addSublayer(circleLayer)
return circleLayer
}
func animateButton()
{
blueLayer = addCircle(color: .blue)
blueLayer!.strokeEnd = 0.0
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 60.0
animation.fromValue = 0.0
animation.toValue = 1.0
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
blueLayer!.strokeEnd = 1.0
blueLayer!.add(animation, forKey: nil)
}

Circular progress in swift

I am trying to make a simple circular progress bar in swift. What I have failed to do so far is, the progress should start at the top of the circle (at the moment it is starting at the 90 degree point). I would also like to have a circular line under the progress line that should be thicker than the progress line and have a different color as well..
Can anyone put me in the right direction towards my wishes?
Here is my function:
func animateView() {
let circle = viewProgress // viewProgress is a UIView
var progressCircle = CAShapeLayer()
let circlePath = UIBezierPath(ovalInRect: circle.bounds)
progressCircle = CAShapeLayer ()
progressCircle.path = circlePath.CGPath
progressCircle.strokeColor = UIColor.greenColor().CGColor
progressCircle.fillColor = UIColor.clearColor().CGColor
progressCircle.lineWidth = 5.0
circle.layer.addSublayer(progressCircle)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1.5
animation.duration = 1
animation.fillMode = kCAFillModeForwards
animation.removedOnCompletion = false
progressCircle.addAnimation(animation, forKey: "ani")
}
Igor this is how it looks:
For Swift 4
Declare two CAShapeLayers. One for actual circle and another for progress Layer!
func createCircularPath() {
let circleLayer = CAShapeLayer()
let progressLayer = CAShapeLayer()
let center = self.view.center
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -.pi / 2, endAngle: 2 * .pi, clockwise: true)
circleLayer.path = circularPath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.lightGray.cgColor
circleLayer.lineCap = .round
circleLayer.lineWidth = 20.0 //for thicker circle compared to progressLayer
progressLayer.path = circularPath.cgPath
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeColor = UIColor.blue.cgColor
progressLayer.lineCap = .round
progressLayer.lineWidth = 10.0
progressLayer.strokeEnd = 0
view.addSublayer(circleLayer)
view.addSublayer(progressLayer)
let progressAnimation = CABasicAnimation(keyPath: "strokeEnd")
progressAnimation.toValue = 1
progressAnimation.fillMode = .forwards
progressAnimation.isRemovedOnCompletion = false
progressLayer.add(progressAnimation, forKey: "progressAnim")
}
Happy Coding!
Hope this answer helps :]

NSValue valueWithCATransform3D: in Swift3

How to translate the following animation into Swift3?
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.5, 2.5, 1)];
What I currently have is
let circleEnlargeAnimation = CABasicAnimation(keyPath: "transform.scale")
circleEnlargeAnimation.fromValue = CATransform3DIdentity
circleEnlargeAnimation.toValue = CATransform3DMakeScale(10.0, 10.0, 1.0)
But my animation is not working... The following is the full animation code:
let shapeLayer = CAShapeLayer()
let center = CGPoint(x: sampleButton.bounds.width/2.0, y: sampleButton.bounds.height/2.0)
let radius = CGFloat(10.0)
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0.0, endAngle: (CGFloat(360.0 * M_PI) / 180.0), clockwise: true)
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineCap = kCALineCapRound
shapeLayer.frame = sampleButton.bounds
let circleEnlargeAnimation = CABasicAnimation(keyPath: "transform.scale")
circleEnlargeAnimation.fromValue = CATransform3DIdentity
circleEnlargeAnimation.toValue = CATransform3DMakeScale(10.0, 10.0, 1.0)
circleEnlargeAnimation.duration = 1.0
shapeLayer.add(circleEnlargeAnimation, forKey: nil)
sampleButton.layer.addSublayer(shapeLayer)
It only shows a circle but is not animating...
You should either use the key transform.scale with a scalar numeric value or transform with a matrix value. See Key Value Coding Extensions in Core Animation Programming Guide for a full list of available keys.
You can create a matrix value for instance with:
NSValue(caTransform3D:CATransform3DMakeScale(10.0, 10.0, 1.0))

Animating circular progress View in Swift

I want to have an animation for my circular progressView with the code below. I want the animation start the progress from 0 to the value I want.
But I can never get the animation work.
func Popular(){
let circle = Pop;//Pop is a UIView
circle.bounds = CGRect(x: 0,y: 0, width: 100, height: 100);
circle.frame = CGRectMake(0,0, 100, 100);
circle.layoutIfNeeded()
var progressCircle = CAShapeLayer();
let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2);
let circleRadius : CGFloat = circle.bounds.width / 2 * 0.83;
let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true );
progressCircle = CAShapeLayer ();
progressCircle.path = circlePath.CGPath;
progressCircle.strokeColor = UIColor.greenColor().CGColor;
progressCircle.fillColor = UIColor.clearColor().CGColor;
UIView.animateWithDuration(3.0,
delay: 1.0,
options: [],
animations: {
progressCircle.lineWidth = 5.0;
progressCircle.strokeStart = 0;
progressCircle.strokeEnd = 0.20;
circle.layer.addSublayer(progressCircle);
circle
progressCircle.strokeEnd = 0.2;
},
completion: { finished in
print("Picutre done")
})
}
Use CABasicAnimation
let circle = Pop
var progressCircle = CAShapeLayer()
let circlePath = UIBezierPath(ovalInRect: circle.bounds)
progressCircle = CAShapeLayer ()
progressCircle.path = circlePath.CGPath
progressCircle.strokeColor = UIColor.greenColor().CGColor
progressCircle.fillColor = UIColor.clearColor().CGColor
progressCircle.lineWidth = 5.0
circle.layer.addSublayer(progressCircle)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 0.2
animation.duration = 3
animation.fillMode = kCAFillModeForwards
animation.removedOnCompletion = false
progressCircle.addAnimation(animation, forKey: "ani")
Two things -
First - Do not re-initialize progressCircle. You are calling progressCircle = CAShapeLayer (); twice. This is not bug here though.
Second - Add your sublayer before you start animating it. Move circle.layer.addSublayer(progressCircle); outside the animation block.

Resources