Timer Progress Bar

Solution for Timer Progress Bar
is Given Below:

I am integrating Timer Progress bar component with animation. But I’m facing an issue on this. When I go to some ViewController and come back means, the progress bar animation should resume from where I stopped. But currently, it is happening from the beginning. Anyone please help on this.

Here is my code..

import Foundation
import UIKit

public protocol TimerProgessBarDelegate: NSObjectProtocol {
    func visualTimerFired(_ timerView: TimerProgessBar?)
}

public class TimerProgessBar: UIView {
    
    enum TimerState {
        case expired
        case pendingMax
        case pendingMin
    }
    
    public weak var delegate: TimerProgessBarDelegate?
    public var timeRemaining: TimeInterval = 0.0
    private var accessoryText: String?
    private var timeOutAccessoryText: String?
    private var backgroundViewColor: UIColor!
    private var timerShapeInactiveColor: UIColor!
    private var timerShapeActiveColor: UIColor!
    private var autohideWhenFired = false
    private var timerView: UIView!
    private var internalTimer: Timer!
    private var timerLabel: UILabel!
    private var timerIcon: UIImageView!
    private var timerPath: UIBezierPath!
    private var backShapeLayer: CAShapeLayer?
    private var shapeLayer: CAShapeLayer?
    private var lastTimeSetting: TimeInterval = 0.0
    private var barThickness: CGFloat = 8.0
    private var timerState: TimerState = .pendingMax
    var updatedTimer: NSNumber?
    
    public init(timeRemaining: TimeInterval, frame: CGRect, accessoryText: String?, timeOutAccessoryText: String?) {
        super.init(frame: frame)
        self.timeOutAccessoryText = timeOutAccessoryText
        self.accessoryText = accessoryText
        
        self.timeRemaining = timeRemaining
        lastTimeSetting = self.timeRemaining
        commonInit()
    }
    
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() {
        backgroundViewColor = UIColor.white
        timerShapeInactiveColor = .tSecondaryDisabledStroke
        
        timerShapeActiveColor = UIColor.gray
        autohideWhenFired = false
        
//        setupView()
    }
    
    // MARK: - Virtual methods
    func reconstructTimerView() {
        for subview in subviews {
            subview.removeFromSuperview()
        }
        
        timerView = UIView(
            frame: CGRect(
                x: 0,
                y: 0,
                width: bounds.size.width,
                height: bounds.size.height))
        timerView.backgroundColor = backgroundViewColor
        
        reloadBarPathWithUpdatedParameters()
        
        resetProgressBar()
        
        timerLabel = UILabel(
            frame: CGRect(
                x: 56,
                y: 19,
                width: bounds.size.width - 66,
                height: 19))
        timerLabel.font = Font.Paragraph.roman
        timerLabel.textColor = .tMidnight
        timerLabel.textAlignment = .left
        timerLabel.attributedText = minutesSecondsString(with: 0)
        timerView?.addSubview(timerLabel)
        
        timerIcon = UIImageView(frame: CGRect(
        x: 15,
        y: 15,
        width: 27,
        height: 27))
//        timerIcon.backgroundColor = .gray
        timerIcon.image = Icon.clockFilled.with(color: .newDisabledBlue)
        timerIcon.roundCorners(corners: UIRectCorner.allCorners, radius: timerIcon.frame.size.width/2)
        timerView?.addSubview(timerIcon)
        
        self.addSubview(timerView)
    }
    
    // MARK: - Setup
    func setupView() {
        self.layer.cornerRadius = 12
        self.layer.masksToBounds = true
        
        reconstructTimerView()
    }
    
    // MARK: - Custom accessors
    func timerIsActive() -> Bool {
        return internalTimer.isValid
    }
    
    // MARK: - Timer view changes
    public func start(timeRemaining: TimeInterval, accessoryText: String?, timeOutAccessoryText: String?) {
        self.timeOutAccessoryText = timeOutAccessoryText
        self.accessoryText = accessoryText
        self.timeRemaining = timeRemaining
        lastTimeSetting = self.timeRemaining
        
        setupView()

        if timeRemaining == 0.0 {
            if timerIcon != nil {
                self.timerIcon.image = Icon.cross.with(color: .onboardingErrorRed)
                timerLabel.textColor = .onboardingErrorRed
                backShapeLayer?.strokeColor = timerShapeInactiveColor.cgColor
                shapeLayer?.strokeColor = timerShapeInactiveColor.cgColor
            }
        }
        else {
            resetTimerView(withTimeRemaining: timeRemaining)
        }
    }
    
    public func stopAndHide() {
        stopTimerView()
        
        if superview != nil {
            removeFromSuperview()
        }
    }
    
    func resetTimerView(withTimeRemaining time: TimeInterval) {
        timeRemaining = time
        lastTimeSetting = timeRemaining
        
        timerLabel.attributedText = minutesSecondsString(with: time)
        startTimerProgressAnimating()
        rechargeTimer(withTime: time)
    }
    
    func stopTimerView() {
        invalidateTimer()
        shapeLayer?.removeFromSuperlayer()
        timerLabel.attributedText = minutesSecondsString(with: 0)
        timeRemaining = lastTimeSetting
        self.timerIcon.image = Icon.cross.with(color: .onboardingErrorRed)
        timerLabel.textColor = .onboardingErrorRed
        backShapeLayer?.strokeColor = timerShapeInactiveColor.cgColor
        animateStateChange()
    }
    
    // MARK: - Timer
    func invalidateTimer() {
        internalTimer.invalidate()
    }
    
    func rechargeTimer(withTime time: TimeInterval) {
        internalTimer = Timer(timeInterval: 1.0, target: self, selector: #selector(internalTimerFired(_:)), userInfo: nil, repeats: true)
        RunLoop.main.add(internalTimer, forMode: .default)
    }
    
    func timerFired() {
        stopTimerView()
        
        if superview != nil {
            if autohideWhenFired {
                removeFromSuperview()
            }
            delegate?.visualTimerFired(self)
        }
    }
    
    @objc func internalTimerFired(_ timer: Timer?) {
        timeRemaining -= 1
        updateView(withTimeRemaining: timeRemaining)
        if timeRemaining < 1 {
            timerFired()
        }
    }
    
    // MARK: - View updates
    func updateView(withTimeRemaining time: TimeInterval) {
        timerLabel.attributedText = minutesSecondsString(with: time)
        setActiveColor()
        shapeLayer?.strokeColor = timerShapeActiveColor.cgColor
        
        updatedTimer = NSNumber(value: time)
    }
    
    // MARK: - Helpers
    func minutesSecondsString(with interval: TimeInterval) -> NSAttributedString? {
        let ti = Int(interval)
        let minutes = (ti / 60)
        let seconds = ti % 60
        
        let minStr = String(format: (minutes < 10) ? "0%li" : "%li", minutes)
        let secStr = String(format: (seconds < 10) ? "0%li" : "%li", seconds)
        
        let boldAttributes = [NSAttributedString.Key.font: Font.Paragraph.bold]
        let timeStr = "(minStr):(secStr)"
        var text = "(accessoryText ?? "") (timeStr) mins"
        if interval <= 0 {
            text = timeOutAccessoryText ?? ""
        }
        
        let timeRange = (text as NSString).range(of: timeStr)
        
        let attributedText = NSMutableAttributedString(string: text)
        attributedText.addAttributes(boldAttributes, range: timeRange)
        return attributedText
    }
    
    func reloadBarPathWithUpdatedParameters() {
        timerPath = UIBezierPath()
        timerPath.lineWidth = barThickness
        
        timerPath.move(
            to: CGPoint(
                x: timerView.bounds.origin.x,
                y: self.bounds.height - barThickness + 4))
        timerPath.addLine(to: CGPoint(x: timerView.bounds.size.width, y: self.bounds.height - barThickness + 4))
    }
    
    func setActiveColor() {
        let greenValue = (lastTimeSetting/3) * 3
        let yellowValue = (lastTimeSetting/3) * 1
        let redValue = 0.0
        var shouldAnimate = false
        switch timeRemaining {
        
           
        case yellowValue ... greenValue:
            self.timerShapeActiveColor = .newDisabledBlue
            if timerIcon != nil {
                timerIcon.image = Icon.clockFilled.with(color: .newDisabledBlue)
            }
            if timerState != .pendingMax {
                shouldAnimate = true
            }
            timerState = .pendingMax
        
        case redValue:
            self.timerShapeActiveColor = .tColourMessageAttention
            if timerIcon != nil {
                timerIcon.image = Icon.clockFilled.with(color: .tColourMessageAttention)
            }
        case redValue ... yellowValue:
            self.timerShapeActiveColor = .tColourMessageAttention
            if timerIcon != nil {
                timerIcon.image = Icon.clockFilled.with(color: .tColourMessageAttention)
            }
            if timerState != .pendingMin {
                shouldAnimate = true
            }
            timerState = .pendingMin
            
        default:
            break
        }
        
        if shouldAnimate {
            shouldAnimate = false
            animateStateChange()
        }
    }
    
    func animateStateChange() {
        UIView.animate(withDuration: 0.5, animations: {
            self.timerIcon.transform = CGAffineTransform(scaleX: 1.10, y: 1.2) }, completion: { (finish: Bool) in
                if finish {
                UIView.animate(withDuration: 0.1, animations: {
                    self.timerIcon.transform = CGAffineTransform.identity
//                    completion(finish)
                })
                }
        })
    }
    
    // MARK: - Animations
    func resetProgressBar() {
        setActiveColor()
        
        
        
        backShapeLayer?.removeFromSuperlayer()
        
        backShapeLayer = CAShapeLayer()
        backShapeLayer?.path = timerPath.cgPath
        backShapeLayer?.strokeColor = timerShapeInactiveColor.cgColor
        backShapeLayer?.fillColor = nil
        backShapeLayer?.lineWidth = barThickness
        backShapeLayer?.lineCap = .square
        backShapeLayer?.zPosition = -1
        if let backShapeLayer = self.backShapeLayer {
            timerView.layer.insertSublayer(backShapeLayer, at: 0)
        }
        
        shapeLayer?.removeFromSuperlayer()
        shapeLayer = CAShapeLayer()
        shapeLayer?.path = timerPath.cgPath
        shapeLayer?.strokeColor = timerShapeActiveColor.cgColor
        shapeLayer?.fillColor = nil
        shapeLayer?.lineWidth = barThickness
        shapeLayer?.lineCap = .square
        shapeLayer?.zPosition = 0
        
        if let shapeLayer = self.shapeLayer {
            timerView.layer.insertSublayer(shapeLayer, at: 1)
        }
    }
    
    func startTimerProgressAnimating() {
        resetProgressBar()

        let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
        pathAnimation.duration = timeRemaining
        pathAnimation.fromValue = NSNumber(value: 1.0)
        pathAnimation.toValue = NSNumber(value: 0.0)
        shapeLayer?.add(pathAnimation, forKey: "strokeEnd")
        
    }
    
}