(Swift) Timer animation not synced with timer display

21 Views Asked by At

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.

0

There are 0 best solutions below