I have a CAShapelayer circle that I animate as a progress circle with CABasicAnimation from 0 back to 360 degrees alongside a countdown timer in statemachine with Reset, Play, Pause, and Complete states.
Right now, I can play or pause and resume until the timer finishes and the animation completes and model goes back to its original values, then pick another countdown timer value to start with animation.
However, if I play and then reset at anytime before animation is complete, it cancels but when I go to play again, the animation no longer works. I am noticing that for some reason after I reset before animation is complete, my strokeEnd doesn't start at 0.0 again and instead gets a seemingly arbitrary decimal point value. I think this is the root cause of my issue, but I don't know why the strokeEnd value are these random numbers. Here's a screenshot of the strokeEnd values - https://imgur.com/a/YXmNkaK
Here's what I have so far:
//draws CAShapelayer
func drawCircle() {}
//CAShapeLayer animation
func progressCircleAnimation(transitionDuration: TimeInterval, speed: Double, strokeEnd: Double) {
let fillLineAnimation = CABasicAnimation(keyPath: "strokeEnd")
fillLineAnimation.duration = transitionDuration
fillLineAnimation.fromValue = 0
fillLineAnimation.toValue = 1.0
fillLineAnimation.speed = Float(speed)
circleWithProgressBorderLayer.strokeEnd = CGFloat(strokeEnd)
circleWithProgressBorderLayer.add(fillLineAnimation, forKey: "lineFill")
}
//Mark: Pause animation
func pauseLayer(layer : CALayer) {
let pausedTime : CFTimeInterval = circleWithProgressBorderLayer.convertTime(CACurrentMediaTime(), from: nil)
circleWithProgressBorderLayer.speed = 0.0
circleWithProgressBorderLayer.timeOffset = pausedTime
}
//Mark: Resume CABasic Animation on CAShaperLayer at pause offset
func resumeLayer(layer : CALayer) {
let pausedTime = circleWithProgressBorderLayer.timeOffset
circleWithProgressBorderLayer.speed = 1.0;
circleWithProgressBorderLayer.timeOffset = 0.0;
circleWithProgressBorderLayer.beginTime = 0.0;
let timeSincePause = circleWithProgressBorderLayer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
circleWithProgressBorderLayer.beginTime = timeSincePause;
}
//Mark: Tried to removeAnimation
func resetLayer(layer : CALayer) {
layer.removeAnimation(forKey: "lineFill")
circleWithProgressBorderLayer.strokeEnd = 0.0
}
Here's how I have it set up in my statemachine, in case the error has to do with this:
@objc func timerIsReset() {
resetTimer()
let currentRow = timePickerView.selectedRow(inComponent: 0)
self.counter = self.Times[currentRow].amount
}
//Mark: action for RunningTimerState
@objc func timerIsStarted() {
runTimer()
}
//Mark: action for PausedTimerState
@objc func timerIsPaused() {
pauseTimer()
}
//Mark: action for TimerTimeRunOutState
func timerTimeRunOut() {
resetTimer()
}
func runTimer() {
if isPaused == true {
finishTime = Date().addingTimeInterval(-remainingTime)
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
resumeLayer(layer: circleWithProgressBorderLayer)
} else if isPaused == false {
finishTime = Date().addingTimeInterval(counter)
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
progressCircleAnimation(transitionDuration: counter, speed: 1.0, strokeEnd: self.completionPercentage)
}
}
//Mark: Pause Timer and pause CABasic Animation
func pauseTimer() {
timer.invalidate()
isPaused = true
remainingTime = -finishTime.timeIntervalSinceNow
completionPercentage = (counter + remainingTime) / counter
pauseLayer(layer: circleWithProgressBorderLayer)
}
func resetTimer() {
timer.invalidate()
isPaused = false
resetLayer(layer: circleWithProgressBorderLayer)
}
speed
to0
. That's all the UIViewPropertyAnimator is really doing under the hood. It isn't magic. – matt