Search code examples
swiftswift2core-animationcabasicanimation

CABasicAnimation reverse(backwards)


I'm a bit struggling with this simple line animation. I figured out how to pause it, but what I need is to be able to reverse animation back to starting point from the moment I call function resetAnimation().

let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
    let pathLayer = CAShapeLayer()

     func lineAnimation() {

        let path = UIBezierPath()
        let screenWidth = self.view.bounds.width
        let screenHeight = self.view.bounds.height
        path.moveToPoint(CGPointMake(screenWidth, screenHeight / 2))
        path.addLineToPoint(CGPointMake(screenWidth - screenWidth, screenHeight / 2))

        self.pathLayer.frame = self.view.bounds
        self.pathLayer.path = path.CGPath
        self.pathLayer.strokeColor = UIColor.whiteColor().CGColor
        self.pathLayer.fillColor = nil
        self.pathLayer.lineWidth = 3.0
        self.pathLayer.lineCap = kCALineCapRound
        self.pathLayer.speed = 1

        self.view.layer.addSublayer(pathLayer)

        self.pathAnimation.duration = 5.0
        self.pathAnimation.fromValue = 0.0
        self.pathAnimation.toValue = 1.0

        pathLayer.addAnimation(pathAnimation, forKey: "animate")

    }

    func pauseAnimation() {

        let pausedTime = pathLayer.convertTime(CACurrentMediaTime(), fromLayer: nil)
        pathLayer.speed = 0
        pathLayer.timeOffset = pausedTime

    }

    func resetAnimation() {


    }

Solution

  • You just need to create a new animation and remove the old one. Your starting point for the new animation will be the current value of that property in your presentation layer. I'd also recommend setting the frame of your shape layer to the bounds of the actual bezier shape instead of the entire view - its a good habit to be in when you start moving things around and scaling/rotating/etc. Otherwise you're gonna be faced with a bunch of funky conversions or anchor point changes.

    Here's what I'd do:

    let pathLayer = CAShapeLayer()
    
    // first, separate your drawing code from your animation code.
    // this way you can call animations without instantiating new objects
    func drawLine() {
        let path = UIBezierPath()
        // draw your path with no position translation.. move the layer
        path.moveToPoint(CGPointMake(view.bounds.width, 0))
        path.addLineToPoint(CGPointMake(0, 0))
        pathLayer.frame = path.bounds
        // this line sets the position of the layer appropriately
        pathLayer.position = view.bounds.width - pathLayer.bounds.width / 2
        pathLayer.path = path.CGPath
        pathLayer.strokeColor = UIColor.whiteColor().CGColor
        pathLayer.fillColor = nil
        pathLayer.lineWidth = 3.0
        pathLayer.lineCap = kCALineCapRound
        view.layer.addSublayer(pathLayer)
    }
    
    func lineAnimation() {
        let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
        pathAnimation.duration = 5.0
        pathAnimation.fromValue = 0.0
        pathAnimation.toValue = 1.0
        pathLayer.addAnimation(pathAnimation, forKey: "strokeEnd")
    
    }
    
    func reverseAnimation() {
        let revAnimation = CABasicAnimation(keyPath: "strokeEnd")
        revAnimation.duration = 5.0
        // every Core Animation has a 'presentation layer' that contains the animated changes
        revAnimation.fromValue = pathLayer.presentationLayer()?.strokeEnd
        revAnimation.toValue = 0.0
        pathLayer.removeAllAnimations()
        pathLayer.addAnimation(revAnimation, forKey: "strokeEnd")
    }
    

    Keep in mind you'll also want to set your properties so you retain the end values of the animation.