How to Use Uiviewcontrolleranimatedtransitioning with Uinavigationcontroller

How to use UIViewControllerAnimatedTransitioning with UINavigationController?

@rounak has the right idea, but sometimes it helps to have code ready without having to download from github.

Here are the steps that I took:

  1. Make your FromViewController.m conform to UINavigationControllerDelegate. Other sample code out there tells you to conform to UIViewControllerTransitioningDelegate, but that's only if you're presenting the ToViewController.

    @interface ViewController : UIViewController

  2. Return your custom transition animator object in the delegate callback method in FromViewController:

    - (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
    animationControllerForOperation:(UINavigationControllerOperation)operation
    fromViewController:(UIViewController *)fromVC
    toViewController:(UIViewController *)toVC {
    TransitionAnimator *animator = [TransitionAnimator new];
    animator.presenting = (operation == UINavigationControllerOperationPush);
    return animator;
    }
  3. Create your custom animator class and paste these sample methods:

    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.5f;
    }

    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    // Grab the from and to view controllers from the context
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    // Set our ending frame. We'll modify this later if we have to
    CGRect endFrame = CGRectMake(80, 280, 160, 100);

    if (self.presenting) {
    fromViewController.view.userInteractionEnabled = NO;

    [transitionContext.containerView addSubview:fromViewController.view];
    [transitionContext.containerView addSubview:toViewController.view];

    CGRect startFrame = endFrame;
    startFrame.origin.x += 320;

    toViewController.view.frame = startFrame;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
    fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
    toViewController.view.frame = endFrame;
    } completion:^(BOOL finished) {
    [transitionContext completeTransition:YES];
    }];
    }
    else {
    toViewController.view.userInteractionEnabled = YES;

    [transitionContext.containerView addSubview:toViewController.view];
    [transitionContext.containerView addSubview:fromViewController.view];

    endFrame.origin.x += 320;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
    toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
    fromViewController.view.frame = endFrame;
    } completion:^(BOOL finished) {
    [transitionContext completeTransition:YES];
    }];
    }
    }

Essentially, the animator is the object doing the heavy lifting. Of course, you can make your UINavigationControllerDelegate be a separate object, but that depends on how your architect your app.

Navigation controller custom transition animation

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.

How to apply custom transition animation in UINavigationControllerDelegate to specific push and pop events?

You Can Check the fromView and ToView controller in below delegate method for perform action

extension BaseViewController: UINavigationControllerDelegate {

func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
if toVc is RandomViewController44{
return InAnimator()
}
return nil
case .pop:
if RandomViewController27 is fromVC{
return OutAnimator()
}
return nil
default:
return nil
}
}
}

You need to check from and toView controller when this method called as set condition as per your requirement

UINavigationControllerDelegate methods, custom transitioning using navigation controller in ios 8

The UINavigationController maintains a weak reference to its delegate. If there is no strong reference then the UINavigationControllerDelegate object is deallocated once the transition is complete.

You can set the delegate object in Storyboard Editor. Add an object by dragging an object from the palette onto the Navigation Controller scene, between the First Responder and the Exit icons. Set its class to that of your delegate object. Make certain the module is your current application module. Then control-drag from the Navigation Controller icon to the object icon and choose "delegate" from the popup menu.

This seems to maintain a strong reference to the delegate object. The delegate object will then be available to invoke the unwind transition. You don't need to create the object yourself or set the delegate reference.

I found this technique in a blog post by Scott James Remnant.

Scott James Remnant - Custom iOS Segues, Transitions, and Animations - The Right Way

Change transition to ONE ViewController in a UINavigationController while keeping back-swipe

You can try to subclass UINavigationController and manage interactivePopGestureRecognizer on your own.

class CustomNavController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
}

extension CustomNavController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === interactivePopGestureRecognizer {
return viewControllers.count > 1
}
return true
}
}

Using UIViewControllerAnimatedTransitioning Without Storyboard Segues

As you correctly pointed out Segues are only available if you use storyboards, so the prepare for segue will not work with xibs

You have created and set the UINavigationControllerDelegate and now just want to start the animation to the other view controller.

Therefore you just need to call push on your navigation view controller:

let vc = DetailViewController(nibName: "DetailViewController", bundle: nil)
self.navigationController.pushViewController(vc, animated: true)

This code creates and presents an instance of DetailViewController (rename to the correct name you use). If you set the delegate for the navigation view controller correctly, your custom animation will be applied.

Custom UIViewController animations WITHOUT A UINAVIGATIONCONTROLLER

You "push" a view controller onto a navigation stack. But when you present a view controller without navigation controller, you name it "present" (not "push"). Means the term "pushing" a view controller is hardly connected to UINavigationController.

So you could just google for something like "UIViewController custom transition" or anything similar.

I did great success following a this great article by Teehan+Lax. Just read it, it should lead you the right way.

EDIT Okay. I gonna add some info to provide a better answer for you.

First of all, you'll create a new subclass of NSObject and conform to the UIViewControllerAnimatedTransitioning protocol:

@interface JWTransitionAnimator <UIViewControllerAnimatedTransitioning>
+ (JWTransitionAnimator *)transitionAnimatorForPresentation;
+ (JWTransitionAnimator *)transitionAnimatorForDismissal;
@end

Then you let the magic happen in your implementation:

@interface JWTransitionAnimator ()
@property (atomic, assign, getter=isPresenting) BOOL presenting;
@end

@implementation JWTransitionAnimator
+ (JWTransitionAnimator *)transitionAnimatorForPresentation {
JWTransitionAnimator *animator = [[self alloc] init];
[animator setPresenting:YES];

return animator;
}
+ (JWTransitionAnimator *)transitionAnimatorForDismissal {
return [[self alloc] init];
}

// implement the protocol:
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitioningContext {
return 0.3; // or any other duration.
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitioningContext {
UIViewController *fromViewController = [transitioningContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext UITransitionContextToViewController];

UIView *fromView = [fromViewController view];
UIView *toView = [toViewController view];

if ([self isPresenting]) {
[fromView setUserInteractionEnabled:NO];

[[transitionContext containerView] addSubview:fromView];
[[transitionContext containerView] addSubview:toView];

[toView setAlpha:0.0];
[toView setTransform:CGAffineTransformScale([fromView transform], 0.3, 0.3)];

[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
[fromView setTintAdjustmentMode:UIViewTintAdjustmentModeDimmed];

[toView setAlpha:1.0];
[toView setTransform:CGAffineTransformScale([toView transform], 1.0, 1.0)];
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}
else {
// reverse your animation here (change "fromView" for "toView" and vice-versa)
}
}

Your presenting viewController must conform to the UIViewControllerTransitionDelegate protocol. Implement it as follows:

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return [JWTransitionAnimator transitionAnimatorForPresentation];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return [JWTransitionAnimator transitionAnimatorForDismissal];
}

The you present the view controller like this:

- (IBAction)presentViewController:(id)sender {
UIViewController *yourVC = [[UIViewController alloc] init]; // however you create it
[yourVC setModalPresentationStyle:UIModalPresentationStyleCustom];
[yourVC setTransitionDelegate:self];

[self presentViewController:yourVC animated:YES completion:NULL];
}


Related Topics



Leave a reply



Submit