Search code examples
swiftanimationuiviewcgaffinetransform

Animate UIView with multiple rotations while translating up and down


I have a button called swirlButton that I'd like to have animate a jump up and down, and have it flip 3 times going up and 3 times back down. I'm using translatedBy to move it, and rotated to rotate it, although in this example I only have it animate part of a turn which goes back the other way when the animation finished.

First of all, I have no idea how to make it rotate more than once in the first place. For instance, I can't set the rotated(by:) value to .pi*3, because that seems just to equal 0 degrees and never animate. Same if I set it to .pi.

var transforms: CGAffineTransform = .identity

let jumpDuration:Double = 2.0 //0.8
let halfJumpDuration:Double = jumpDuration/2.0

UIView.animateKeyframes(withDuration: jumpDuration, delay: 0, options: [UIView.KeyframeAnimationOptions.calculationModeCubicPaced], animations: {

    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: halfJumpDuration/jumpDuration, animations: {
        transforms = transforms.translatedBy(x: 0, y: -60)
        transforms = transforms.rotated(by: .pi/2)
        swirlButton.transform = transforms
    })

    UIView.addKeyframe(withRelativeStartTime: halfJumpDuration/jumpDuration, relativeDuration: halfJumpDuration/jumpDuration, animations: {
        transforms = .identity
        transforms = transforms.translatedBy(x: 0, y: 0)
        swirlButton.transform = transforms
    })

},
   completion: { _ in

        print("animation finished")

})

Aside from going up and down, the rotation is very far from what I would like to happen. Is it difficult to make it spin counterclockwise 3 times going up and continue spinning counterclockwise 3 times going down?


Solution

  • I think it's easier to use CABasicAnimation for this.

    Here's what I came up with:

    func animateButton() {
      swirlButton.layer.add(rotateAnimation(), forKey: nil)
    
      CATransaction.begin()
      let upAnimation = bounceAnimation()
      CATransaction.setCompletionBlock{ () in
        self.swirlButton.layer.add(self.bounceAnimation(animatingDown: true), forKey: nil)
      }
      swirlButton.layer.add(upAnimation, forKey: nil)
      CATransaction.commit()
    }
    
    func rotateAnimation() -> CABasicAnimation {
      let rotate = CABasicAnimation(keyPath: "transform.rotation")
      rotate.fromValue = 0
      rotate.toValue = -6*CGFloat.pi
      rotate.duration = 2
      return rotate
    }
    
    func bounceAnimation(animatingDown: Bool = false) -> CABasicAnimation {
      let buttonY = swirlButton.layer.position.y
      let buttonX = swirlButton.layer.position.x
      let translate = CABasicAnimation(keyPath: "position")
      translate.fromValue = animatingDown ? [buttonX, buttonY - 200] : [buttonX, buttonY]
      translate.toValue = animatingDown ? [buttonX, buttonY] : [buttonX, buttonY - 200]
      translate.duration = 1
      translate.fillMode = .forwards
      translate.isRemovedOnCompletion = false
      return translate
    }
    

    translate.fillMode = .forwards & translate.isRemovedOnCompletion = false is necesarry to prevent flicker in-between the animations and CATransaction allows us to set a completion block for when the first animation (up) finishes.