To do a custom transition with navigation controller (UINavigationController
), you should:
-
Define your view controller to conform to
UINavigationControllerDelegate
protocol. For example, you can have a private class extension in your view controller’s.m
file that specifies conformance to this protocol:@interface ViewController () <UINavigationControllerDelegate> @end
-
Make sure you actually specify your view controller as your navigation controller’s delegate:
- (void)viewDidLoad { [super viewDidLoad]; self.navigationController.delegate = self; }
-
Implement
animationControllerForOperation
in 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, e.g.:
@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
That does fade transition, but you should feel free to customize the animation as you see fit.
-
If you want to handle interactive gestures (e.g. something like the native swipe left-to-right to pop), you have to implement an interaction controller:
-
Define a property for an interaction controller (an object that conforms to
UIViewControllerInteractiveTransitioning
):@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController;
This
UIPercentDrivenInteractiveTransition
is a nice object that does the heavy lifting of updating your custom animation based upon how complete the gesture is. -
Add a gesture recognizer to your view. Here I’m just implementing the left gesture recognizer to simulate a 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 delegate, you also have to implement
interactionControllerForAnimationController
delegate method- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController { return self.interactionController; }
-
If you google “UINavigationController custom transition tutorial” and you’ll get many hits. Or see WWDC 2013 Custom Transitions video.