CABasicAnimation gets completed when showing another vc modally

532 Views Asked by At

I use the following way to pause/resume animation

func pauseAnimation(){
  var pausedTime = layer.convertTime(CACurrentMediaTime(), fromLayer: nil)
  layer.speed = 0.0
  layer.timeOffset = pausedTime
}

func resumeAnimation(){
  var pausedTime = layer.timeOffset
  layer.speed = 1.0
  layer.timeOffset = 0.0
  layer.beginTime = 0.0
  let timeSincePause = layer.convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime
  layer.beginTime = timeSincePause
}

It works like a charm as long as the ViewController is currently presented.

When I present modally another view controller and then dismiss it, animation is done, no matter how much time elapsed during this present/dismiss action.

Do you have any suggestions why this might happen? How to fix it? I'd like to add that all other views holds their state, only the animation is completed.

EDIT:

I just figured out that it happens regardles of pausing/resuming - ongoing animation gets completed as well in such scenario.

Here is my code that shows animation implementation

import Foundation

import UIKit

class CircleView: UIView {

var circleLayer: CAShapeLayer!

@IBOutlet var view: UIView!


@IBOutlet weak var progressLabel: UILabel!

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    Bundle.main.loadNibNamed("CircleView", owner: self, options: nil)
    self.addSubview(view)
    view.frame = self.bounds

}

override func awakeFromNib() {
        super.awakeFromNib()
        setupAppearance()

}

func setupAppearance() {
    progressLabel.textColor = UIColor.textColor
    progressLabel.font = UIFont.textTimerClock
}


func setup(progress:Double, clockwise:Bool) {

    self.backgroundColor = UIColor.clear
    var strokeColor = UIColor.positiveProgressColor
    if !clockwise { strokeColor = UIColor.positiveProgressColor }

    // Use UIBezierPath as an easy way to create the CGPath for the layer.
    // The path should be the entire circle.
    let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(.pi * 2 * progress), clockwise: clockwise)

    // Setup the CAShapeLayer with the path, colors, and line width
    circleLayer = CAShapeLayer()
    circleLayer.path = circlePath.cgPath
    circleLayer.fillColor = UIColor.clear.cgColor
    circleLayer.strokeColor = strokeColor.cgColor
    circleLayer.lineWidth = 8.0;

    // Don't draw the circle initially
    circleLayer.strokeEnd = 0.0

    // Add the circleLayer to the view's layer's sublayers
    layer.addSublayer(circleLayer)

    //add grey path
    let greyCircleLayer = CAShapeLayer()

    let greyCirclePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(.pi * 2.0), clockwise: true)

    greyCircleLayer.path = greyCirclePath.cgPath
    greyCircleLayer.fillColor = UIColor.clear.cgColor
    greyCircleLayer.strokeColor = UIColor.appLightGrey.cgColor
    greyCircleLayer.lineWidth = 1.0;

    // Don't draw the circle initially
    circleLayer.strokeEnd = 0.0

    // Add the circleLayer to the view's layer's sublayers
    layer.insertSublayer(greyCircleLayer, below: circleLayer)
    if progressLabel != nil {

        progressLabel.text = "10"}
}

func pauseAnimation(){
    let pausedTime = circleLayer.convertTime(CACurrentMediaTime(), from: nil)
    circleLayer.speed = 0.0
    circleLayer.timeOffset = pausedTime
}

func resumeAnimation(){
    let pausedTime = circleLayer.timeOffset
    circleLayer.speed = 1.0
    circleLayer.timeOffset = 0.0
    circleLayer.beginTime = 0.0
    let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    circleLayer.beginTime = timeSincePause
}


func animateCircle(duration: TimeInterval, color: UIColor) {
    // We want to animate the strokeEnd property of the circleLayer
            circleLayer.strokeColor = color.cgColor
    let animation = CABasicAnimation(keyPath: "strokeEnd")

    // Set the animation duration appropriately
    animation.duration = duration

    // Animate from 0 (no circle) to 1 (full circle)
    animation.fromValue = 0
    animation.toValue = 1

    // Do a linear animation (i.e. the speed of the animation stays the same)
    animation.timingFunction = CAMediaTimingFunction(name: kCAAnimationLinear)

    // Set the circleLayer's strokeEnd property to 1.0 now so that it's the
    // right value when the animation ends.
    circleLayer.strokeEnd = 1.0

    // Do the actual animation
    circleLayer.add(animation, forKey: "animateCircle")
}
}

Another thing worth mentioning: init(), awakeFromNib() are not being called again so this is not the case.

Another: The same happens for pushing VC instead of presenting modally.

1

There are 1 best solutions below

0
On

In general when you display another view controller, the view of current view controller is removed from the window. This will also remove all pending animations from their layers, and any existing animation completion handler is called with false, because the animation is not completed (see also https://stackoverflow.com/a/21200504/2352344).

To continue the animation after returning to the view controller, you should reconstruct the animation object in viewWillAppear.