Search code examples
iosanimation3drotationcaanimation

iOS 3DAnimations: Issues when rotating multiple superposed views


Issue

When flipping (e.g. pi rotation) views at the same time on the same axis, the view hierarchy (which one is in front and which one is behind) seems to be disturbed.

Detailed explanations

In the GIF below, the grey view with the swift icon is in front and the blue view is behind.

You can notice that when the first rotation of the grey view reaches pi / 2, its transparency seems to be altered. In addition, when the grey finishes its first rotation, instead of staying behind the blue view, it appears to be in front during all the next rotation.

With blue coloured supView

Now repeating the exact same animation, the blue view has now no colours applied but it is still in the view hierarchy behind the grey view, and still animated.

no colour view

Desired Result

I am trying to achieved the same animation keeping the blue view on top of the grey view, without having any apparent transparency change on the grey view. I use UIViewPropertyAnimator to be able to scrub the animation later on.

The Code

The following code describes a simplified flip method (as an UIView extension) the animation is based on.

extension UIView{


    func flipping(_ times: Int = 1, to direction: FoldingDirection, within duration: Double = 0.01, startAfter delay: Double = 0) {
        guard times > 0 else { return }


        // resets the layer anchor point depending on the current transform states of the view (calling a specific method)
        self.setAnchorPoint(at: direction.anchor())

        // adding perspective effect 
        self.layer.transform.m34 = 1 / 250

        // setting rotation axis          
        let axis: CGPoint = {
            switch direction {
            case .right, .left:
                return CGPoint(x: 1, y: 0)
            default:
                return CGPoint(x: 0, y: 1)
            }
        }()

        let flipDuration = duration > 0 ? duration / Double(times) : 0.01

        // creating animator
        let animator = UIViewPropertyAnimator(duration: flipDuration, curve: .linear) {
            self.layer.transform = CATransform3DRotate(self.layer.transform, .pi, axis.y, axis.x, 0)
        }

        animator.startAnimation(afterDelay: delay)

        animator.addCompletion { (position) in
            if position == .end {
                self.flipping(times - 1, to: direction, within: duration * ( 1 - 1 / Double(times)), startAfter: 0)
            }   
        }
    }
}

Calling the animation:

greyView.flipping(3, to: .right, within: 9, startAfter: 0)
blueView.flipping(2, to: .right, within: 6, startAfter: 3)

Thanks for your help!

EDIT After running multiple tests, it seems the view hierarchy is indeed changed during the animation: the rotation seems to be occurring behind the view plane.


Solution

  • In this kind of animations, their Zpostion is not assigned so that you cannot control which one is closer and the other one is farther.

    Assign zPosition to layers at event time will fix your problems. You may compare

          greyView.layer.zPosition = 20
           blueView.layer.zPosition = -20
    

    and

             greyView.layer.zPosition = 200
           blueView.layer.zPosition = -200
    

    and you will know what I mean.