I am trying to add spinner effect to a circular view.
func spin() {
let circlePathLayer = CAShapeLayer()
circlePathLayer.fillColor = nil
circlePathLayer.strokeColor = UIColor.redColor.cgColor
circlePathLayer.lineWidth = 6
layer.addSublayer(circlePathLayer)
setPath(to: circlePathLayer)
animate(layer: circlePathLayer)
}
private func setPath(to layer: CAShapeLayer) {
layer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: layer.lineWidth / 2,
dy: layer.lineWidth / 2)).cgPath
}
private func animate(layer: CAShapeLayer) {
animateKeyPath(keyPath: "strokeEnd",
duration: 1,
values: [0, 1],
layer: layer)
animateKeyPath(keyPath: "strokeStart",
duration: 1,
values: [(0 - 0.1) as CGFloat, (1 - 0.1) as CGFloat],
layer: layer)
}
private func animateKeyPath(keyPath: String, duration: CFTimeInterval, values: [CGFloat], layer: CAShapeLayer) {
let animation = CAKeyframeAnimation(keyPath: keyPath)
animation.values = values
animation.calculationMode = .linear
animation.duration = duration
animation.repeatCount = Float.infinity
layer.add(animation, forKey: animation.keyPath)
}
But for the above code, at the end of animation duration
, a flicker happens every time at 1
position of the strokeEnd
. How could I overcome the flicker and regulate the spinner?
Just to notice the effect visually, I have set the duration to 5 seconds. Please notice when the spinner reaches 3'o clock position.
I think strokeStart
and strokeEnd
are constrained to be between 0 and 1. When you try to set strokeStart to a negative number (like -0.1) the system pins it to zero.
Try this solution:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class SpinnerView : UIView {
let circlePathLayer = CAShapeLayer()
override func didMoveToSuperview() {
layer.addSublayer(circlePathLayer)
}
func spin() {
let circleFrame = bounds.insetBy(dx: -40, dy: -40)
circlePathLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
circlePathLayer.strokeColor = UIColor.red.cgColor
circlePathLayer.fillColor = nil
circlePathLayer.lineWidth = 6
setPath(to: circlePathLayer)
animate(layer: circlePathLayer)
}
private func setPath(to layer: CAShapeLayer) {
let path = UIBezierPath()
path.addArc(withCenter: CGPoint(x: layer.bounds.midX,
y: layer.bounds.midY),
radius: (bounds.width / 2.0) - layer.lineWidth / 2,
startAngle: 0,
endAngle: 2 * .pi / 10.0,
clockwise: true)
layer.path = path.cgPath
}
private func animate(layer: CAShapeLayer) {
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.duration = 1
animation.repeatCount = Float.infinity
animation.fromValue = 0
animation.toValue = 2 * CGFloat.pi
layer.add(animation, forKey: "transform.rotation")
}
}
class MyViewController : UIViewController {
let spinner = SpinnerView()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
spinner.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(spinner)
view.addConstraints([
NSLayoutConstraint(item: spinner,
attribute: .centerX,
relatedBy: .equal,
toItem: view,
attribute: .centerX,
multiplier: 1.0,
constant: 0),
NSLayoutConstraint(item: spinner,
attribute: .centerY,
relatedBy: .equal,
toItem: view,
attribute: .centerY,
multiplier: 1.0,
constant: 0),
NSLayoutConstraint(item: spinner,
attribute: .width,
relatedBy: .equal,
toItem: view,
attribute: .width,
multiplier: 1.0,
constant: -40)
])
spinner.addConstraints([
NSLayoutConstraint(item: spinner,
attribute: .width,
relatedBy: .equal,
toItem: spinner,
attribute: .height,
multiplier: 1.0,
constant: 0)
])
self.view = view
}
override func viewDidLayoutSubviews() {
spinner.spin()
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()