When you push or pop a View controller, the Navigation controller will ask its delegate to provide the animation controller.

If the method returns nil, the Navigation Controller will use the default animation. If an object is returned, the Navigation Controller will use this object as the custom Transition Animation Controller.

Animation controller to follow UIViewControllerAnimatedTransitioning protocol.

When an animation controller object (or animator) is provided, the navigation controller calls the following methods:

The Navigation Controller calls the transitionDuration() method to determine how long the animation needs to be executed, and our custom animation code needs to be written in the animateTransition() method.

Now we’ll implement a custom UINavigationController push animation that looks like this:

Core code for a custom animation class RevealAnimator:

import UIKit

class RevealAnimator: NSObject,UIViewControllerAnimatedTransitioning,CAAnimationDelegate {

    letAnimationDuration = 2.0 var operation: UINavigationControllerOperation =. Push / / Since you 're going to create some layer animationsfor your transition, you’ll need to store the animation context somewhere until the animation ends and the delegate method animationDidStop(_:finished:) executes. At that point, you’ll call completeTransition() from within animationDidStop() to wrap up the transition.
    weak var storedContext: UIViewControllerContextTransitioning?
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return animationDuration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    
        ifOperation ==. Push {storedContext = transitionContext // You're trying to cast the "from" view controller to a MasterViewController instancewhich is true only for the push transition, but not forThe pop transition // If it is not push but pop, it will crash, so it needs to be addedifjudgelet fromVC = transitionContext.viewController(forKey:.from) as! MasterViewController
            let toVC = transitionContext.viewController(forKey:.to) as! DetailViewController
            transitionContext.containerView.addSubview(toVC.view)
            toVC.view.frame = transitionContext.finalFrame(for: toVC)
            
            let animation = CABasicAnimation(keyPath: "transform") animation.fromValue = NSValue(caTransform3D: CATransform3DIdentity) animation.toValue = NSValue(caTransform3D: CATransform3DConcat (CATransform3DMakeTranslation (0.0, 10.0, 0.0), CATransform3DMakeScale (150.0, 150.0, 1.0))) / /set the duration of the animation to match the transition duration
            animation.duration = animationDuration
            // setThe animator as the delegate animation. Delegate = self // configure the animation model to leave the animation on screen.this avoids glitches when the transition wraps up since the RW logo will be hidden away anyway animation.fillMode  = kCAFillModeForwards animation.isRemovedOnCompletion =false
            // add easing to make the reveal effect accelerate over time.
            animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseIn)
            
            //  This creates a CAShapeLayer to be applied to the DestinationViewController. The maskLayer is positioned inThe same location as the "RW" logo on the MasterViewController. Then you simplysetMaskLayer as the mask of the View controller's view.let maskLayer: CAShapeLayer = RWLogoLayer.logoLayer()
            maskLayer.position = fromVC.logo.position
            toVC.view.layer.mask = maskLayer
            maskLayer.add(animation, forKey: nil)
            
            // make the original logo grow with the mask, matching its shape exactly
            fromVC.logo.add(animation, forKey: nil)
            
            // fade in the new view controller as the reveal animation runs
            let fadeIn = CABasicAnimation(keyPath: "opacity") fadeIn) fromValue = 0.0 fadeIn) = 1.0 fadeIn toValue. Duration = animationDuration toVC. The layer. The add (fadeIn,forKey: nil)
        } else{/ / pop animationlet fromView = transitionContext.view(forKey: .from)!
            let toView = transitionContext.view(forKey: .to)! / / will fromView displayed on toView, back to the previous page surface transitionContext. ContainerView. InsertSubview (toView, belowSubview: Animate (withDuration: animationDuration, delay: 0.0, options:.curveeasein, animations: {// Use an animation to scale fromView to 0.01. Don't Use 0.0forScaling -- This will confuse UIKit. For this animation you can use an ordinary view animation -- there's no need to create A Layer animation.fromView. transform = CGAffineTransform(scaleX: 0.01, y: 0.01)}, completion: {_intransitionContext.completeTransition(! transitionContext.transitionWasCancelled) }) } } // check whether you have a stored transition context;ifso, you call completeTransition() on it. This passes the ball back to the navigation controller to wrap up with the Func animationDidStop(_ anim: CAAnimation, finished Flag: Bool) {if letcontext = storedContext { context.completeTransition(! context.transitionWasCancelled) // reset logolet fromVC = context.viewController(forKey: .from) as! MasterViewController fromVC.logo.removeAllAnimations() // remove the mask after the view has appeared and the transition  is complete.let toVC = context.viewController(forKey: .to) as! DetailViewController
            toVC.view.layer.mask = nil
        }
        storedContext = nil
    }
}
Copy the code

Download the demo

By analogy, it’s not hard to imagine creating custom Transitions for UITabBarController as well. It works similarly to the UINavigationController transformation, and we can easily create it based on what we’ve learned so far.