swiftperspectivecabasicanimationcatransform3drotate

iOS Swift - How to do 360 degree flip animation using CABasicAnimation and CATransform3DRotate with "m34" transform?


Despite all my best efforts, I can't figure out how to do a full cycle rotation animation of a UIView using CATransform3DRotate from left to right. Kindly note that I can already rotate it 360 degree with CABasicAnimation, but without CATransform3DRotate. However, I want to use .m34 transform for perspective depth. Solutions involving UIView.animate/transitions are not helpful in what I am trying to accomplish. Any help is much appreciated.

Edit: I searched far and wide in Stackoverflow and other places, but didn't find anything that describes how to do 360 rotation with CATransform3DRotate. Calling top guns out there for help as this is a question that does not seem to have been answered before. Thanks!

Here is the code that rotates UIView 180 degree.

var transform = CATransform3DIdentity
transform.m34 = 1.0 / -200.0

let animation = CABasicAnimation(keyPath: "transform")
animation.toValue = CATransform3DRotate(transform, CGFloat( Double.pi), 0, 1, 0)
animation.duration = 0.8
animation.beginTime = 0.0
animation.fillMode = .forwards
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
cardView.layer.add(animation, forKey: "360flip")

Solution

  • Your current code is replacing the entire transform of the animating layer, meaning you can't apply the perspective transform to it. You also have issues with the end state being mathematically identical to the beginning state, and to cope with this you're trying to chain animations together, and using fill modes, which makes the whole thing more complicated.

    First, let's deal with the transform. To get the perspective you want to set the .m34 of the layer's transform, which you're already doing:

    var perspective = CATransform3DIdentity
    perspective.m34 = 1 / -200
    cardView.layer.transform = perspective
    

    Next, to only touch the rotation of the layer's transform, you can use a more precise key path than transform:

    let rotate = CABasicAnimation(keyPath: "transform.rotation.y")
    rotate.fromValue = 0
    

    This will leave the rest of the layer's transform intact.

    Finally, how to force the animation engine to notice a difference when the end of the animation is identical to the start? Use byValue:

    rotate.byValue = CGFloat.pi * 2
    

    This forces the layer to take the long way round to 360 degrees, instead of the lazy shortcut of staying exactly where it is.

    The final, complete code is:

    var perspective = CATransform3DIdentity
    perspective.m34 = 1 / -200
    cardView.layer.transform = perspective
    let rotate = CABasicAnimation(keyPath: "transform.rotation.y")
    rotate.fromValue = 0
    rotate.byValue = CGFloat.pi * 2
    rotate.duration = 2
    cardView.layer.add(rotate, forKey: nil)
    

    Giving this result:

    A label rotating 360 degrees with perspective