BasicAnimation, Swift - ios

I defined a circle like this :
let progressLayer = CAShapeLayer()
let ring = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2 , endAngle: 2 * CGFloat.pi , clockwise: true)
progressLayer.path = ring.cgPath
progressLayer.strokeColor = UIColor.green.cgColor
progressLayer.fillColor = UIColor.clear.cgColor
view.layer.addSublayer(progressLayer)
Then I defined an animation that fill the border of my circle with a color and duration like this:
let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
basicAnimation.toValue = 1
basicAnimation.duration = 60 //Seconds !
basicAnimation.fillMode = .forwards
progressLayer.add(basicAnimation, forKey: "blablabla")
What happened is that the animation comes to the end of the circle with a delay of 12 seconds! (The circle is filled but 12 seconds left)
Why does this happened? Is it a trigonometry problem (startAngle endAngle)? I can't understand ...

There is an issue with the bezier path you are adding an extra quarter to the circle,
So Circle is filled at (60 / 5) * 4 = 48 seconds and the other 12 seconds is overdrawing on an already filled quarter.
Setting start Angle to 0 will fix the issue.
let ring = UIBezierPath(arcCenter: center, radius: 100, startAngle: CGFloat(0) , endAngle: 2 * CGFloat.pi , clockwise: true)

Related

Sync a stroke fill animation based on a UIProgressView status?

Currently we have a circular progress bar that is working properly, for testing purposes it is activated by a tap gesture.
func createCircleShapeLayer() {
let center = view.center
if UIDevice.current.userInterfaceIdiom == .pad {
let circlePath = UIBezierPath(arcCenter: center, radius: 150, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
circleShapeLayer.path = circlePath.cgPath
} else {
let circlePath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
circleShapeLayer.path = circlePath.cgPath
}
circleShapeLayer.strokeColor = UIColor.green.cgColor
circleShapeLayer.lineWidth = 20
circleShapeLayer.fillColor = UIColor.clear.cgColor
circleShapeLayer.lineCap = CAShapeLayerLineCap.round
circleShapeLayer.strokeEnd = 0
view.layer.addSublayer(circleShapeLayer)
//tap gesture used for animation testing
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(anitmateCirleProgress)))
}
#objc func anitmateCirleProgress() {
let strokeAnimation = CABasicAnimation(keyPath: strokeEnd)
strokeAnimation.toValue = 1
strokeAnimation.duration = 2
strokeAnimation.fillMode = .forwards
strokeAnimation.isRemovedOnCompletion = false
circleShapeLayer.add(strokeAnimation, forKey: "strokeAnimation")
}
The issue is that the progress bar has to be able to fill based on the status of a UIProgressView: ex.
totalProgressView.setProgress(45, animated: true)
Is there a way to sync the animation stroke based on the progress of the UIProgressView?
I thought a UIProgressView took a progress value from 0 to 1? If so, your sample code that sets the progress view to 45 doesn't make sense.
UIProgressView and a shape layer's strokeEnd both use values from 0 to 1, so you should be able to just set both the progress view and your custom shape layer's strokeEnd to the same fractional value and get them both to show the same amount of completion.

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)
}

I want to Rotate 3 different images on different location in 360 degree in swift 4 using UIBezierPath

I want to Rotate 3 different images on different location in 360 degree in swift 4 using UIBezierPath. i am able to move single image like this in image. with this code
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let circlePath = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX, y: view.frame.midY), radius: 120, startAngle: 0, endAngle:CGFloat(Double.pi)*2, clockwise: true)
let animation = CAKeyframeAnimation(keyPath: "position");
animation.duration = 5
animation.repeatCount = MAXFLOAT
animation.path = circlePath.cgPath
let moon = UIImageView()
moon.frame = CGRect(x:0, y:0, width:40, height:40);
moon.image = UIImage(named: "moon.png")
view.addSubview(moon)
moon.layer.add(animation, forKey: nil)
}
}
and I want to rotate 3 different images on different Angle and Position like this.i want to rotate all 3 different images like in this
Anyone here who can update my code according to the image above I want Thank You in Advance Cheers! :) .
I tried with your code and its working. I made a function to rotate a imageView.
func startMovingAView(startAnlge: CGFloat, endAngle: CGFloat) {
let circlePath = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX, y: view.frame.midY), radius: 120, startAngle: startAnlge, endAngle:endAngle, clockwise: true)
let animation = CAKeyframeAnimation(keyPath: "position");
animation.duration = 5
animation.repeatCount = MAXFLOAT
animation.path = circlePath.cgPath
let moon = UIImageView()
moon.frame = CGRect(x:0, y:0, width:40, height:40);
moon.image = UIImage(named: "moon.png")
view.addSubview(moon)
moon.layer.add(animation, forKey: nil)
}
And here is the angles what I pass for each imageView:
/// For first imageView
var startAngle: CGFloat = 0.0
var endAngle: CGFloat = CGFloat(Double.pi) * 2.0
startMovingAView(startAnlge: startAngle, endAngle: endAngle)
/// For second imageView
startAngle = CGFloat(Double.pi/2.0)
endAngle = CGFloat(Double.pi) * 2.5
startMovingAView(startAnlge: startAngle, endAngle: endAngle)
/// For third imageView
startAngle = CGFloat(Double.pi)
endAngle = CGFloat(Double.pi) * 3.0
startMovingAView(startAnlge: startAngle, endAngle: endAngle)
We just need to find the angles with same difference so that in 5 seconds duration the imageView have to travel the same distance so all objects moves with constant and same speed.

How to make circle based on percentage?

I can create a circle, but instead I need it to start at "12:00" and go to a certain percentage.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 100,y: 100), radius: CGFloat(20), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.CGPath
//change the fill color
shapeLayer.fillColor = UIColor.clearColor().CGColor
//you can change the stroke color
shapeLayer.strokeColor = UIColor.redColor().CGColor
//you can change the line width
shapeLayer.lineWidth = 3.0
cell.progress.layer.addSublayer(shapeLayer)
You need to adjust the start and end angles to meet your criteria.
"12:00" on the circle would be 3pi/2 or -pi/2 (- CGFloat.pi / 2). A full circle is 2pi (2 * CGFloat.pi). A percentage of that is of course CGFloat.pi * 2 * percentage. So the end would be your start + that percentage.
let circlePath = UIBezierPath(
arcCenter: CGPoint(x: 100, y: 100),
radius: 20,
startAngle: - .pi / 2,
endAngle: - .pi / 2 + .pi * 2 * percentage ,
clockwise: true
)
where percentage is some CGFloat in the range 0.0 to 1.0.

Using UIBeizerPath on Circle

I want to make a circle which can be cut by percent, for example, 0.25 is 1/4 of the circle.
This is my code so far:
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 100,y: 300), radius: CGFloat(20), startAngle: CGFloat(M_PI + M_PI_2), endAngle:CGFloat(90 * M_PI / 180, clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.CGPath
//change the fill color
shapeLayer.fillColor = UIColor.clearColor().CGColor
//you can change the stroke color
shapeLayer.strokeColor = UIColor.redColor().CGColor
//you can change the line width
shapeLayer.lineWidth = 3.0
view.layer.addSublayer(shapeLayer)
In startAngle I wrote CGFloat(M_PI + M_PI_2) because the start of the circle doesnt start in 0 but as in this picture:
In the endAngle line I wrote CGFloat(90 * M_PI / 180) because the formula from degrees to radians is: degrees*pi/180 .
BUT! In the view I get half of the circle and not quarter of the circle:
Does my endAngle formula is wrong?
I think what you want is for your start angle to be 0 and your end angle to be 90 * PI/180
What you have now is a curve that starts at the bottom 270, aka M_PI + M_PI/2 and goes to 90 (90 * M_PI / 180)
//Possibly what you want is for your endAngle = (90 * M_PI/180) + startAngle , aka a quarter more than where you started, i'm not sure which quarter of the circle you want
Because in iOS the top of the circle starts at 270 degrees, the startAngle is:
startAngle: CGFloat(M_PI + M_PI_2)
And the endAngle is:
endAngle:CGFloat((360 * 0.25 - 90)
Instead of 0.25, you can change it to any number between 0 to 1 (for example: if you want half of the circle to be full, change it to 0.5)

Resources