Search code examples
ios12swift4.2xcode10.1

How Do I Animate a CALayer's Position?


Please have a look at the following method which I am using to move a sublayer 50 points up along the y axis. In particular, pay attention to the last line (which is commented out).

@objc func animateButtonPressed(_ sender: UIButton) {
    let initialValue = customView.sublayer.position.y
    let finalValue = customView.sublayer.position.y - 50

    let animation = CABasicAnimation(keyPath: "position.y")
    animation.fromValue = initialValue
    animation.toValue = finalValue
    animation.duration = 2
    customView.sublayer.add(animation, forKey: "Reposition")

    //customView.sublayer.position.y = finalValue
}

With the code as shown, I get the following behavior:

  1. The layer slowly moves from the initial position to the final position.
  2. The layer then jumps back to the initial position.
  3. The layer remains at the initial position.

Everything I read informs me that the "jumping back" occurs because I have not updated the model to reflect the final value. So, let's update the model by uncommenting the final line. Now the behavior is:

  1. The layer jumps from the initial position to the final position.
  2. The layer then jumps back to the initial position.
  3. The layer then slowly moves to the final position.
  4. The layer remains at the final position.

The jump is apparently occurring because the final position is being set on the model. I feel like I am in a catch 22. Any help will be much appreciated.

A working project that illustrates the problem can be found on GitHub

Xcode 10.0, iOS 12.2, Swift 4.2


Solution

  • Here's the canonical form as developed and explained in my book. Set the animated layer to its final value beforehand but with implicit animation turned off, like this:

    @objc func animateButtonPressed(_ sender: UIButton) {
        let initialValue = customView.sublayer.position.y
        let finalValue = customView.sublayer.position.y - 50
        CATransaction.setDisableActions(true) //       <-- *
        customView.sublayer.position.y = finalValue // <-- *
        let animation = CABasicAnimation(keyPath: "position.y")
        animation.fromValue = initialValue
        animation.toValue = finalValue
        animation.duration = 2
        customView.sublayer.add(animation, forKey: "Reposition")
    }