2

I'm trying to implement two consecutive transformation animations. When the first animation ends, the second animation is called through the completion handler. Because this is a transformation animation, my issue is that when the first animation finishes, the layer resizes back to the original size, and then the second animation begins. I'd like for the second animation to begin with the new layer size after the first transformation animation. This post Objective-C - CABasicAnimation applying changes after animation? says I have to resize/transform the layer before beginning the first animation, so that when the first animation ends, the layer is actually the new size. I've tried to do that by changing the bounds or actually applying the transform to the layer but its still not working.

override func viewDidAppear(_ animated: Bool) {

    buildBar()   

}

func buildBar(){

    progressBar1.bounds = CGRect(x: 0, y: 0, width: 20, height: 5)
    progressBar1.position = CGPoint(x: 0, y: 600)
    progressBar1.backgroundColor = UIColor.white.cgColor
    view.layer.addSublayer(progressBar1)
    extendBar1()

}


func extendBar1(){

     CATransaction.begin()

     let transform1 = CATransform3DMakeScale(10, 1, 1)
     let anim = CABasicAnimation(keyPath: "transform")
     // self.progressBar1.bounds = CGRect(x: 0, y: 0, width: 200, height: 5)
     // self.progressBar1.transform = transform1
     anim.isRemovedOnCompletion = false
     anim.fillMode = kCAFillModeForwards
     anim.toValue = NSValue(caTransform3D:transform1)
     anim.duration = 5.00

     CATransaction.setCompletionBlock {

         self.extendBar2()
     }

     progressBar1.add(anim, forKey: "transform")
     CATransaction.commit()
}

func extendBar2(){

     let transform1 = CATransform3DMakeScale(2, 1, 1)
     let anim = CABasicAnimation(keyPath: "transform")
     anim.isRemovedOnCompletion = false
     anim.fillMode = kCAFillModeForwards
     anim.toValue = NSValue(caTransform3D:transform1)
     anim.duration = 5.00
     progressBar1.add(anim, forKey: "transform")

}
Community
  • 1
  • 1
Brosef
  • 2,945
  • 6
  • 34
  • 69

1 Answers1

1

Because you are modifying the transform property of the layer in both animation, it will be easier here to use CAKeyframeAnimation, which will handle the chaining of the animations for you.

func extendBar(){
     let transform1 = CATransform3DMakeScale(10, 1, 1)
     let transform2 = CATransform3DMakeScale(2, 1, 1)

     let anim = CAKeyframeAnimation()
     anim.keyPath = "transform"
     anim.values = [progressBar1.transform, transform1, transform2] // the stages of the animation
     anim.keyTimes = [0, 0.5, 1] // when they occurs, 0 being the very begining, 1 the end
     anim.duration = 10.00

     progressBar1.add(anim, forKey: "transform")
     progressBar1.transform = transform2 // we set the transform property to the final animation's value
}

A word about the content of values and keyTimes:

  • We set the first value to be the current transform of progressBar1. This will make sure we start in the current layer's state.
  • In keyTimes, we say that at the begining, the first value in the values array should be used. We then say at animation's half time, the layer should be transformed in values second value. Hence, the animation between the initial state and the second one will occurs during that time. Then, between 0.5 to 1, we'll go from tranform1 to transform2.

You can read more about animations in this very nice article from objc.io.


If you really need two distinct animations (because maybe you want to add subviews to progressBar1 in between, here's the code that will do it

func extendBar1() {
    CATransaction.begin()
    CATransaction.setCompletionBlock {
        print("side effects")
        extendBar2()
    }
    let transform1 = CATransform3DMakeScale(5, 1, 1)
    let anim = CABasicAnimation(keyPath: "transform")

    anim.fromValue = progressBar1.transform
    anim.toValue = transform1
    anim.duration = 2.00
    progressBar1.add(anim, forKey: "transform")
    CATransaction.setDisableActions(true)
    progressBar1.transform = transform1
    CATransaction.commit()
}

func extendBar2() {
    CATransaction.begin()
    let transform2 = CATransform3DMakeScale(2, 1, 1)
    let anim = CABasicAnimation(keyPath: "transform")

    anim.fromValue = progressBar1.transform
    anim.toValue = transform2
    anim.duration = 2.00
    progressBar1.add(anim, forKey: "transform")
    CATransaction.setDisableActions(true)
    progressBar1.transform = transform2
    CATransaction.commit()
}

What happens here? Basically, we setup a first "normal animation". So we create the animation, that will modify the presentation layer and set the actual layer to the final first animation's transform.

Then, when the first animation completes, we call extendBar2 which will in turn queue a normal animation.

You also want to call CATransaction.setDisableActions(true) before explicitly updating the transform, otherwise, core animation will create an implicit one, which will override the one created just before.

tomahh
  • 13,441
  • 3
  • 49
  • 70
  • Is it possible to use the completion handler with this? I'm trying to do something in between the two animations thats why I have the completion handler. – Brosef Mar 01 '17 at 02:49
  • Hum, no, you there are no callbacks when certain points of the keyframe animation are reached. Let me a minute to extend my answer with two different animations, so that you can use the completion handler. – tomahh Mar 01 '17 at 02:50
  • @Brosef one minute turned into 12, but answer was updated :) – tomahh Mar 01 '17 at 03:04
  • Is this working for you? For me, after the first animation, it animates backwards to the original size. – Brosef Mar 01 '17 at 03:13
  • Yep, just tried it in a playground, every works fine. – tomahh Mar 01 '17 at 03:18
  • hmm. If I just do the first animation, it ends where it is suppose to. If I include the call to the second animation in the completion handler, it just resets to the original size and doesn't animate. – Brosef Mar 01 '17 at 03:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/136912/discussion-between-tomahh-and-brosef). – tomahh Mar 01 '17 at 03:20