Custom Navigation Controller Transition Animation

I follow some tutorials for creating custom animations when moving from one view to another.

My test project using custom segue from here works fine, but someone told me that it is no longer encouraged to do custom animation in the user segment, and I should use UIViewControllerAnimatedTransitioning .

I followed several tutorials that use this protocol, but they all relate to modal presentations (like this tutorial ).

What I'm trying to do is click sega in the navigation controller tree, but when I try to do the same with show (push) segue, it no longer works.

Please tell me the correct way to make a custom transitional animation from one view to another in the navigation controller.

Still, can I use one method for all transition animations? It would be inconvenient if one day I want to make the same animation, but in the end I will have to duplicate the code twice to work on the transition from modal vs controller.

+72
ios cocoa-touch custom-transition
Oct 26 '14 at 3:22
source share
3 answers

To perform a custom transition with a navigation controller ( UINavigationController ), you must:

  • Define your view controller to comply with the UINavigationControllerDelegate protocol. For example, you can have a private class extension in your .m view controller that indicates compliance with this protocol:

     @interface ViewController () <UINavigationControllerDelegate> @end 
  • Make sure you actually specify your view controller as the delegate of the navigation controller:

     - (void)viewDidLoad { [super viewDidLoad]; self.navigationController.delegate = self; } 
  • Paste animationControllerForOperation into your view controller:

     - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController*)fromVC toViewController:(UIViewController*)toVC { if (operation == UINavigationControllerOperationPush) return [[PushAnimator alloc] init]; if (operation == UINavigationControllerOperationPop) return [[PopAnimator alloc] init]; return nil; } 
  • Implement animators for push and pop animations, for example:

     @interface PushAnimator : NSObject <UIViewControllerAnimatedTransitioning> @end @interface PopAnimator : NSObject <UIViewControllerAnimatedTransitioning> @end @implementation PushAnimator - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; [[transitionContext containerView] addSubview:toViewController.view]; toViewController.view.alpha = 0.0; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ toViewController.view.alpha = 1.0; } completion:^(BOOL finished) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end @implementation PopAnimator - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view]; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.alpha = 0.0; } completion:^(BOOL finished) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end 

    This will lead to a gradual transition, but you can freely customize the animation as you wish.

  • If you want to handle interactive gestures (for example, something like scrolling from left to right to pop), you need to implement an interaction controller:

    • Define a property for the interaction controller (an object that corresponds to UIViewControllerInteractiveTransitioning ):

       @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController; 

      This UIPercentDrivenInteractiveTransition is a good object that does a heavy upgrade upgrade for user animation based on how complete the gesture is.

    • Add a gesture recognizer to your view. Here I simply implement the left gesture recognizer to simulate pop:

       UIScreenEdgePanGestureRecognizer *edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFromLeftEdge:)]; edge.edges = UIRectEdgeLeft; [view addGestureRecognizer:edge]; 
    • Implement the gesture recognizer handler:

       /** Handle swipe from left edge * * This is the "action" selector that is called when a left screen edge gesture recognizer starts. * * This will instantiate a UIPercentDrivenInteractiveTransition when the gesture starts, * update it as the gesture is "changed", and will finish and release it when the gesture * ends. * * @param gesture The screen edge pan gesture recognizer. */ - (void)handleSwipeFromLeftEdge:(UIScreenEdgePanGestureRecognizer *)gesture { CGPoint translate = [gesture translationInView:gesture.view]; CGFloat percent = translate.x / gesture.view.bounds.size.width; if (gesture.state == UIGestureRecognizerStateBegan) { self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init]; [self popViewControllerAnimated:TRUE]; } else if (gesture.state == UIGestureRecognizerStateChanged) { [self.interactionController updateInteractiveTransition:percent]; } else if (gesture.state == UIGestureRecognizerStateEnded) { CGPoint velocity = [gesture velocityInView:gesture.view]; if (percent > 0.5 || velocity.x > 0) { [self.interactionController finishInteractiveTransition]; } else { [self.interactionController cancelInteractiveTransition]; } self.interactionController = nil; } } 
    • In your navigation controller deletion, you must also implement the interactionControllerForAnimationController delegate method

       - (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController { return self.interactionController; } 

If you google "UINavigationController custom transition tutorial" and you will get many hits. Or see WWDC Videoconferencing 2013 .

+191
Oct 26 '14 at 4:06 on
source share

You can add the following code to addSubview

  toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; 

From another question custom-transition-for-push-animation-with-navigationcontroller-on-ios-9

From the Apple documentation for finalFrameForViewController:

Returns the destination frame rectangle for the specified view view controllers.

The rectangle returned by this method represents the size of the corresponding view at the end of the transition. For presentation during a presentation, the value returned by this method may be CGRectZero, but it may also be a valid box rectangle.

+14
Apr 10 '16 at 19:39
source share

Using Rob and Q, I have perfect answers, here is a simplified Swift code using the same fade animation for .push and .pop:

 extension YourViewController: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { //INFO: use UINavigationControllerOperation.push or UINavigationControllerOperation.pop to detect the 'direction' of the navigation class FadeAnimation: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.5 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) if let vc = toViewController { transitionContext.finalFrame(for: vc) transitionContext.containerView.addSubview(vc.view) vc.view.alpha = 0.0 UIView.animate(withDuration: self.transitionDuration(using: transitionContext), animations: { vc.view.alpha = 1.0 }, completion: { finished in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) } else { NSLog("Oops! Something went wrong! 'ToView' controller is nill") } } } return FadeAnimation() } } 

Remember to specify the delegate in the ViewViewView () method in the YourViewController view:

 override func viewDidLoad() { //... self.navigationController?.delegate = self //... } 
+5
Nov 08 '17 at 12:55 on
source share



All Articles