Animación de transición personalizada del controlador de navegación

He seguido algunos tutoriales para crear animaciones personalizadas durante la transición de una vista a otra.

Mi proyecto de prueba con segue personalizado desde aquí funciona bien, pero alguien me dijo que ya no se anima a hacer animaciones personalizadas en una transición personalizada, y debería usar UIViewControllerAnimatedTransitioning .

Seguí varios tutoriales que hacen uso de este protocolo, pero todos se refieren a la presentación modal (por ejemplo, este tutorial ).

Lo que estoy tratando de hacer es una seguación de inserción dentro de un árbol de controlador de navegación, pero cuando trato de hacer lo mismo con un show (push) segue ya no funciona.

Por favor, dígame la forma correcta de hacer animaciones de transición personalizadas de una vista a otra en un controlador de navegación.

¿Y de todos modos puedo usar un método para todas las animaciones en transición? Sería incómodo si un día quiero hacer la misma animación pero terminar teniendo que duplicar el código dos veces para trabajar en la transición modal vs controlador.

Para hacer una transición personalizada con el controlador de navegación ( UINavigationController ), debe hacer lo siguiente:

  • Defina su controlador de vista para cumplir con el protocolo UINavigationControllerDelegate . Por ejemplo, puede tener una extensión de clase privada en el archivo .m su controlador de vista que especifique la conformidad con este protocolo:

     @interface ViewController ()  @end 
  • Asegúrese de especificar realmente su controlador de vista como delegado de su controlador de navegación:

     - (void)viewDidLoad { [super viewDidLoad]; self.navigationController.delegate = self; } 
  • Implemente animationControllerForOperation en su controlador de vista:

     - (id)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; } 
  • Implementar animadores para animaciones push y pop, por ejemplo:

     @interface PushAnimator : NSObject  @end @interface PopAnimator : NSObject  @end @implementation PushAnimator - (NSTimeInterval)transitionDuration:(id )transitionContext { return 0.5; } - (void)animateTransition:(id)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 )transitionContext { return 0.5; } - (void)animateTransition:(id)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 

    Eso se desvanece transición, pero debe sentirse libre de personalizar la animación como mejor le parezca.

  • Si desea manejar gestos interactivos (por ejemplo, algo así como el deslizamiento nativo de izquierda a derecha para hacer estallar), debe implementar un controlador de interacción:

    • Defina una propiedad para un controlador de interacción (un objeto que se ajuste a UIViewControllerInteractiveTransitioning ):

       @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController; 

      Esta UIPercentDrivenInteractiveTransition es un buen objeto que hace el trabajo pesado de actualizar tu animación personalizada en función de lo completo que sea el gesto.

    • Agregue un reconocedor de gestos a su vista. Aquí solo estoy implementando el reconocedor de gestos izquierdo para simular un pop:

       UIScreenEdgePanGestureRecognizer *edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFromLeftEdge:)]; edge.edges = UIRectEdgeLeft; [view addGestureRecognizer:edge]; 
    • Implementar el manejador del reconocedor de gestos:

       /** 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; } } 
    • En su delegado de controlador de navegación, también debe implementar interactionControllerForAnimationController método de delegado interactionControllerForAnimationController

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

Si busca en google “UINavigationController tutorial de transición personalizado” obtendrá muchos éxitos. O vea el video WWDC 2013 Custom Transitions .

Puede agregar el siguiente código antes de addSubview

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

De otra pregunta custom-transition-for-push-animation-with-navigationcontroller-on-ios-9

De la documentación de Apple para finalFrameForViewController:

Devuelve el rectángulo del cuadro final para la vista del controlador de vista especificado.

El rectángulo devuelto por este método representa el tamaño de la vista correspondiente al final de la transición. Para la vista que se cubre durante la presentación, el valor devuelto por este método podría ser CGRectZero, pero también podría ser un rectángulo válido.

Usando las respuestas perfectas de Rob’s & Q, aquí está el código Swift simplificado, usando la misma animación de desvanecimiento para .push y .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() } } 

No olvides configurar el delegado en el método viewDidLoad () de YourViewController:

 override func viewDidLoad() { //... self.navigationController?.delegate = self //... } 

Funciona tanto rápido 3 como 4

  @IBAction func NextView(_ sender: UIButton) { let newVC = self.storyboard?.instantiateViewControllerWithIdentifier(withIdentifier: "NewVC") as! NewViewController let transition = CATransition() transition.duration = 0.5 transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = kCATransitionPush transition.subtype = kCAGravityLeft //instead "kCAGravityLeft" try with different transition subtypes self.navigationController?.view.layer.add(transition, forKey: kCATransition) self.navigationController?.pushViewController(newVC, animated: false) }