I'm trying recreate Apple's timer for an exercise app. However, the circle progress animation doesn't match the timer display. For example, I'm counting down from a minute, the animation starts 12 seconds or so after the timer starts ticking down. The longer the time I'm counting down from, the longer the delay is before the animation starts. Interestingly, the animation ends around a second before the display does. Any help is appreciated.
Timer.swift
import UIKit
class TrainingSession: UIViewController {
@IBOutlet weak var timerLabel: UILabel!
let shapeLayer = CAShapeLayer()
var timeLeft = 60
var timePast = 0
var timer = Timer()
var timeStringFormatter = TimeStringGetter()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.addSublayer(shapeLayer)
let center = view.center
//draws the track under the timer
let tracklayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: 150, startAngle: -.pi / 2, endAngle: 2 * .pi, clockwise: true)
tracklayer.path = circularPath.cgPath
tracklayer.strokeColor = UIColor.darkGray.cgColor
tracklayer.lineWidth = 10
tracklayer.lineCap = CAShapeLayerLineCap.round
tracklayer.fillColor = UIColor.clear.cgColor
view.layer.addSublayer(tracklayer)
//draws the time left
shapeLayer.path = circularPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 10
//shapeLayer.strokeStart = 0
shapeLayer.strokeEnd = 1
shapeLayer.lineCap = CAShapeLayerLineCap.round
shapeLayer.fillColor = UIColor.clear.cgColor
view.layer.addSublayer(shapeLayer)
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
}
@objc private func handleTap() {
let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
basicAnimation.duration = 60 //duration of timer in seconds, should take value of exerciseTime
basicAnimation.toValue = 0
basicAnimation.fillMode = CAMediaTimingFillMode.forwards
basicAnimation.isRemovedOnCompletion = false
shapeLayer.add(basicAnimation, forKey: "Bing Bong")
//logic to handle timer updating
timer.invalidate()
timePast = 0
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo:nil, repeats: true)
}
@objc func updateTimer() {
if timePast < timeLeft {
timerLabel.text = timeStringFormatter.getString(timeData: timeLeft-timePast)
timePast += 1
} else {
timer.invalidate()
timerLabel.text = "00:00"
}
}
}
SecondsToString.swift
import Foundation
struct TimeStringGetter {
func getString(timeData:Int) -> String {
//time left is greater than a minute
if timeData >= 60 {
var minutes: Int
minutes = timeData/60
let seconds = timeData%60
if seconds >= 10 {
return "0\(minutes):\(seconds)"
} else {
return "0\(minutes):0\(seconds)"
}
}
//double digit seconds
if(timeData >= 10){
return "00:\(timeData)"
}
//single digit seconds
return "00:0\(timeData)"
}
}
I tried adding changing the values of animation duration to see what happens. If the time is ticking down from a shorter period, say 10 seconds, there isn't a significant delay compared to when I'm counting down from a whole minute.