Search code examples
swiftanimationuikitcore-animationcalayer

Fill the CALayer with different color when timer reaches to 5 seconds swift ios


In my app i am using a third party library which does circular animation just like in the appstore app download animation. i am using the external file which i have placed in my project. it works but when the timer reaches to 5 seconds the fill color should be red. Currently the whole layer red color applies, i want to only apply the portion it has progressed. i am attaching the video and the third party file. Please can anyone help me with this as what changes should i make to the library file or is there better solution

same video link in case google drive link doesnt work

External Library link which i am using

video link

external third file file

My code snippet that i have tried:

func startGamePlayTimer(color: UIColor)
    {
        if !isCardLiftedUp {
            self.circleTimerView.isHidden = false
            self.circleTimerView.timerFillColor = color
            Constants.totalGamePlayTime = 30
            self.circleTimerView.startTimer(duration: CFTimeInterval(Constants.totalGamePlayTime))
        }
       
        if self.gamePlayTimer == nil
        {
            self.gamePlayTimer?.invalidate()
         
            let timer = Timer.scheduledTimer(timeInterval: 1.0,
                                                   target: self,
                                                   selector: #selector(updateGamePlayTime),
                                                   userInfo: nil,
                                                   repeats: true)
            timer.tolerance = 0.05
            RunLoop.current.add(timer, forMode: .common)
            self.gamePlayTimer = timer
        }
    }
    
    @objc func updateGamePlayTime()
    {
        if Constants.totalGamePlayTime > 0
        {
            Constants.totalGamePlayTime -= 1
            
            if Constants.totalGamePlayTime < 5
            {
                SoundService.playSound(sound: .turn_timeout)
                self.circleTimerView.timerFillColor = UIColor.red.withAlphaComponent(0.7)
            }
        }
        else
        {
            self.stopGamePlayTimer()
        }
    }

where circleTimerView is the view which animates as the time progresses


Solution

  • You can achieve that with CAKeyframeAnimation on the StrokePath.

    I've Update the code in CircleTimer View as below. You can modified fill color and time as per your need.

     @objc func updateGamePlayTime()
        {
            if Constants.totalGamePlayTime > 0
            {
                Constants.totalGamePlayTime -= 1
                
                if Constants.totalGamePlayTime < 5
                {
                    SoundService.playSound(sound: .turn_timeout)
                    // self.circleTimerView.timerFillColor = UIColor.red.withAlphaComponent(0.7) // <- Comment this line 
                }
            }
            else
            {
                self.stopGamePlayTimer()
            }
        }
    

    open func drawFilled(duration: CFTimeInterval = 5.0) {
        clear()
        if filledLayer == nil {
            let parentLayer = self.layer
            let circleLayer = CAShapeLayer()
            circleLayer.bounds = parentLayer.bounds
            circleLayer.position = CGPoint(x: parentLayer.bounds.midX, y: parentLayer.bounds.midY)
            let circleRadius = timerFillDiameter * 0.5
            let circleBounds = CGRect(x: parentLayer.bounds.midX - circleRadius, y: parentLayer.bounds.midY - circleRadius, width: timerFillDiameter, height: timerFillDiameter)
            circleLayer.fillColor = timerFillColor.cgColor
            circleLayer.path = UIBezierPath(ovalIn: circleBounds).cgPath
            
            // CAKeyframeAnimation: Changing Fill Color on when timer reaches 80% of total time
    
            let strokeAnimation = CAKeyframeAnimation(keyPath: "fillColor")
            strokeAnimation.keyTimes = [0, 0.25, 0.75, 0.80]
            strokeAnimation.values = [timerFillColor.cgColor, timerFillColor.cgColor, timerFillColor.cgColor, UIColor.red.withAlphaComponent(0.7).cgColor]
            strokeAnimation.duration = duration;
            circleLayer.add(strokeAnimation, forKey: "fillColor")
            
            parentLayer.addSublayer(circleLayer)
            filledLayer = circleLayer
        }
    }
    

    open func startTimer(duration: CFTimeInterval) {
        drawFilled(duration: duration) // <- Need to pass duration here  
        if useMask {
            runMaskAnimation(duration: duration)
        } else {
            runDrawAnimation(duration: duration)
        }
    }
    

    enter image description here

    UPDATE:

    CAKeyframeAnimation:

    • You specify the keyframe values using the values and keyTimes properties.

    For example:

    let colorKeyframeAnimation = CAKeyframeAnimation(keyPath: "backgroundColor")
    
    colorKeyframeAnimation.values = [UIColor.red.cgColor,
                                     UIColor.green.cgColor,
                                     UIColor.blue.cgColor]
    colorKeyframeAnimation.keyTimes = [0, 0.5, 1]
    colorKeyframeAnimation.duration = 2
    

    This animation will run for the 2.0 duration.

    • We have 3 values and 3 keyTimes in this example.
    • 0 is the initial point and 1 be the last point.
    • It shows which associated values will reflect at particular interval [keyTimes] during the animation.

    i.e:

    KeyTime 0.5 -> (2 * 0.5) -> At 1 sec during animation -> UIColor.green.cgColor will be shown.

    Now to answer your question from the comments, Suppose we have timer of 25 seconds and we want to show some value for last 10 seconds, we can do:

            strokeAnimation.keyTimes = [0, 0.25, 0.50, 0.60]
            strokeAnimation.values = [timerFillColor.cgColor,timerFillColor.cgColor, timerFillColor.cgColor, timerFillColor.cgColor, UIColor.red.withAlphaComponent(0.7).cgColor]
    strokeAnimation.duration = 25.0 // Let's assume duration is 25.0