Cómo cambiar las animaciones Push y Pop en una aplicación basada en navegación

Tengo una aplicación basada en la navegación y quiero cambiar la animación de las animaciones push y pop. ¿Como podría hacerlo?

Hice lo siguiente y funciona bien … y es simple y fácil de entender …

CATransition* transition = [CATransition animation]; transition.duration = 0.5; transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade //transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom [self.navigationController.view.layer addAnimation:transition forKey:nil]; [[self navigationController] popViewControllerAnimated:NO]; 

Y lo mismo para push …


Versión de Swift 3.0:

 let transition = CATransition() transition.duration = 0.5 transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = kCATransitionFade self.navigationController?.view.layer.add(transition, forKey: nil) _ = self.navigationController?.popToRootViewController(animated: false) 

Así es como siempre he logrado completar esta tarea.

Para Push:

 MainView *nextView=[[MainView alloc] init]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:0.75]; [self.navigationController pushViewController:nextView animated:NO]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO]; [UIView commitAnimations]; [nextView release]; 

Para Pop:

 [UIView beginAnimations:nil context:NULL]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationDuration:0.75]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO]; [UIView commitAnimations]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDelay:0.375]; [self.navigationController popViewControllerAnimated:NO]; [UIView commitAnimations]; 

Todavía recibo muchos comentarios de esto, así que voy a continuar y actualizarlo para usar bloques de animación, que es la forma recomendada por Apple de hacer animaciones de todos modos.

Para Push:

 MainView *nextView = [[MainView alloc] init]; [UIView animateWithDuration:0.75 animations:^{ [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [self.navigationController pushViewController:nextView animated:NO]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO]; }]; 

Para Pop:

 [UIView animateWithDuration:0.75 animations:^{ [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO]; }]; [self.navigationController popViewControllerAnimated:NO]; 

para empujar

 CATransition *transition = [CATransition animation]; transition.duration = 0.3; transition.type = kCATransitionFade; //transition.subtype = kCATransitionFromTop; [self.navigationController.view.layer addAnimation:transition forKey:kCATransition]; [self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO]; 

para el pop

 CATransition *transition = [CATransition animation]; transition.duration = 0.3; transition.type = kCATransitionFade; //transition.subtype = kCATransitionFromTop; [self.navigationController.view.layer addAnimation:transition forKey:kCATransition]; [self.navigationController popViewControllerAnimated:NO]; 

@Magnus respuesta, solo entonces para Swift (2.0)

  let transition = CATransition() transition.duration = 0.5 transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = kCATransitionPush transition.subtype = kCATransitionFromTop self.navigationController!.view.layer.addAnimation(transition, forKey: nil) let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController self.navigationController?.pushViewController(writeView, animated: false) 

Algunas notas al margen:

Puede hacer esto también con Segue, simplemente implemente esto en prepareForSegue o shouldPerformSegueWithIdentifier . Sin embargo , esto mantendrá la animación predeterminada en él también. Para solucionarlo, debe ir al guión gráfico, hacer clic en Segue y desmarcar la casilla ‘Animates’. Pero esto limitará su aplicación para iOS 9.0 y superior (al menos cuando lo hice en Xcode 7).

Al hacer en un segue, las últimas dos líneas deben reemplazarse por:

 self.navigationController?.popViewControllerAnimated(false) 

Aunque establecí falso, de alguna manera lo ignora.

Recuerda que en Swift , la extensión es definitivamente tus amigos!

 public extension UINavigationController { /** Pop current view controller to previous view controller. - parameter type: transition animation type. - parameter duration: transition animation duration. */ func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) { self.addTransition(transitionType: type, duration: duration) self.popViewControllerAnimated(false) } /** Push a new view controller on the view controllers's stack. - parameter vc: view controller to push. - parameter type: transition animation type. - parameter duration: transition animation duration. */ func push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) { self.addTransition(transitionType: type, duration: duration) self.pushViewController(vc, animated: false) } private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) { let transition = CATransition() transition.duration = duration transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = type self.view.layer.addAnimation(transition, forKey: nil) } } 

Usar llamadas privadas es una mala idea ya que Apple ya no aprueba las aplicaciones que hacen eso. Tal vez podrías probar esto:

 //Init Animation [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration: 0.50]; [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES]; //Create ViewController MyViewController *myVC = [[MyViewController alloc] initWith...]; [self.navigationController pushViewController:myVC animated:NO]; [myVC release]; //Start Animation [UIView commitAnimations]; 

Cómo cambiar las animaciones Push y Pop en una aplicación basada en navegación …

Para 2018, ¡la “respuesta final”!

Preámbulo Diga que es nuevo en el desarrollo de iOS, tal vez de Android. Muy confuso, Apple proporciona dos (2) transiciones que se pueden usar fácilmente. Estos son: “crossfade” y “flip”. Ahora, las dos transiciones más inútiles en toda la informática son “crossfade” y “flip”, que nadie usa. Si desea hacer las transiciones más comunes, como “deslizarse”, debe realizar una GRAN cantidad de trabajo. Ese trabajo, se explica en este post!

  • En primer lugar, si desea utilizar uno de los dos animes que proporciona Apple (crossfade, voltea), es simple: utilice la solución de @PeterDeWeese anterior.

  • En segundo lugar, está el antiguo rápido y sucio CATransition quickfix. Eso se explica en detalle aquí . Realmente no funciona, y no es una solución realista.

De lo contrario, sorprendentemente, tienes que hacer el esfuerzo de hacer una transición personalizada completa .

Repetir:

incluso si solo quiere la transición más simple, más común, mover-mover / mover , para bien o para mal, tiene que implementar una transición personalizada completa.

He aquí cómo hacerlo …

1. Necesita un UIViewControllerAnimatedTransitioning personalizado

  1. Necesitas un bool propio como popStyle . (¿Está apareciendo o desapareciendo?)

  2. Debe incluir transitionDuration (trivial) y la llamada principal, animateTransition

  3. De hecho, tendrá que escribir dos rutinas diferentes para el interior de animateTransition . Uno para el empuje, y otro para el pop. Probablemente animatePush nombre animatePush y animatePop . Dentro de animateTransition , simplemente ramifique en popStyle a las dos rutinas

  4. El ejemplo a continuación realiza un movimiento / desvío simple

  5. En sus rutinas animatePush y animatePop . Debe obtener “de vista” y “para ver”. (How-to se muestra en el ejemplo)

  6. y lo principal que debes hacer es, de hecho, addSubview una addSubview para la nueva vista “a”.

  7. debes llamar a Transición completeTransition al final de tu anime

Asi que ..

  class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning { var popStyle: Bool = false func transitionDuration( using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.20 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { if popStyle { animatePop(using: transitionContext) return } let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! let f = transitionContext.finalFrame(for: tz) let fOff = f.offsetBy(dx: f.width, dy: 55) tz.view.frame = fOff transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view) UIView.animate( withDuration: transitionDuration(using: transitionContext), animations: { tz.view.frame = f }, completion: {_ in transitionContext.completeTransition(true) }) } func animatePop(using transitionContext: UIViewControllerContextTransitioning) { let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! let f = transitionContext.initialFrame(for: fz) let fOffPop = f.offsetBy(dx: f.width, dy: 55) transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view) UIView.animate( withDuration: transitionDuration(using: transitionContext), animations: { fz.view.frame = fOffPop }, completion: {_ in transitionContext.completeTransition(true) }) } } 

Y entonces …

2. Úselo en su controlador de vista.

Tenga en cuenta que solo tiene que hacer esto en el “primer” controlador de vista.

En el que apareces arriba, no haces nada. Fácil.

Entonces tu clase …

 class SomeScreen: UIViewController { } 

se convierte …

 class FrontScreen: UIViewController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate { let simpleOver = SimpleOver() override func viewDidLoad() { super.viewDidLoad() navigationController?.delegate = self } func navigationController( _ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { simpleOver.popStyle = (operation == .pop) return simpleOver } } 

Eso es.

Empuje y explote exactamente como lo hace normalmente, sin cambios. Para empujar …

 let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil) .instantiateViewController(withIdentifier: "nextScreenStoryboardID") as! NextScreen navigationController?.pushViewController(n, animated: true) 

y para abrirlo, puede hacerlo solo si lo hace en la siguiente pantalla:

 class NextScreen: TotallyOrdinaryUIViewController { @IBAction func userClickedBackOrDismissOrSomethingLikeThat() { navigationController?.popViewController(animated: true) } } 

Es “así de simple”: O

Dado que este es el resultado principal en Google, pensé que compartiría lo que creo que es la manera más sensata; que es usar la API de transición iOS 7+. Implementé esto para iOS 10 con Swift 3.

Es bastante simple combinar esto con cómo anima UINavigationController entre dos controladores de vista si crea una subclase de UINavigationController y devuelve una instancia de una clase que se ajuste al protocolo UIViewControllerAnimatedTransitioning .

Por ejemplo, aquí está mi subclase UINavigationController :

 class NavigationController: UINavigationController { init() { super.init(nibName: nil, bundle: nil) delegate = self } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension NavigationController: UINavigationControllerDelegate { public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return NavigationControllerAnimation(operation: operation) } } 

Puede ver que configuro el UINavigationControllerDelegate para sí mismo, y en una extensión en mi subclase implemento el método en UINavigationControllerDelegate que le permite devolver un controlador de animación personalizado (es decir, NavigationControllerAnimation ). Este controlador de animación personalizado reemplazará la animación de stock por usted.

Probablemente se esté preguntando por qué paso la operación a la instancia de NavigationControllerAnimation través de su inicializador. Hago esto para que en la implementación de UIViewControllerAnimatedTransitioning protocolo UIViewControllerAnimatedTransitioning sé cuál es la operación (es decir, ‘push’ o ‘pop’). Esto ayuda a saber qué tipo de animación debo hacer. La mayoría de las veces, desea realizar una animación diferente dependiendo de la operación.

El rest es bastante estándar. Implemente las dos funciones requeridas en el protocolo UIViewControllerAnimatedTransitioning y anime como desee:

 class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning { let operation: UINavigationControllerOperation init(operation: UINavigationControllerOperation) { self.operation = operation super.init() } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.3 } public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return } let containerView = transitionContext.containerView if operation == .push { // do your animation for push } else if operation == .pop { // do your animation for pop } } } 

Es importante recordar que para cada tipo diferente de operación (es decir, ‘push’ o ‘pop’), los controladores de vista y de vista serán diferentes. Cuando se encuentra en una operación de inserción, el controlador para ver será el que se está presionando. Cuando se encuentre en una operación pop, el controlador de vista será el que se está haciendo la transición, y el controlador de vista será el que se abrirá.

Además, el controlador to ver se debe agregar como una subvista de containerView en el contexto de transición.

Cuando finalice la animación, debe llamar a transitionContext.completeTransition(true) . Si está haciendo una transición interactiva, tendrá que devolver dinámicamente un Bool para completeTransition(didComplete: Bool) , dependiendo de si la transición se completa al final de la animación.

Finalmente ( lectura opcional ), es posible que desee ver cómo hice la transición en la que estaba trabajando. Este código es un poco más hacky y lo escribí bastante rápido, así que no diría que es un gran código de animación, pero muestra cómo hacer la parte de animación.

La mía fue una transición realmente simple; Quería imitar la misma animación que normalmente hace UINavigationController, pero en lugar de la animación de la ‘siguiente página sobre la parte superior’, quería implementar una animación 1: 1 del viejo controlador de vista a la vez que la nueva vista controlador aparece. Esto tiene el efecto de hacer que los dos controladores de vista parezcan fijados entre sí.

Para la operación de inserción, eso requiere primero configurar el origen de la vista del toViewController de vista en el eje x fuera de pantalla, agregarlo como subvista de la vista de containerView , animarlo en la pantalla estableciendo ese origin.x a cero. Al mismo tiempo, fromViewController la vista desde origin.x al establecer su origin.x fuera de la pantalla:

 toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0) containerView.addSubview(toViewController.view) UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [ UIViewAnimationOptions.curveEaseOut ], animations: { toViewController.view.frame = containerView.bounds fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0) }, completion: { (finished) in transitionContext.completeTransition(true) }) 

La operación pop es básicamente la inversa. Agregue toViewController como una subvista de containerView y aleje fromViewController el fromViewController a la derecha mientras anima en el toViewController desde la izquierda:

 containerView.addSubview(toViewController.view) UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: [ UIViewAnimationOptions.curveEaseOut ], animations: { fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0) toViewController.view.frame = containerView.bounds }, completion: { (finished) in transitionContext.completeTransition(true) }) 

Aquí hay una esencia con todo el archivo rápido:

https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be

Hay UINavigationControllerDelegate y UIViewControllerAnimatedTransitioning allí puede cambiar la animación para cualquier cosa que desee.

Por ejemplo, esta es una animación pop vertical para VC:

 @objc class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.5 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let containerView = transitionContext.containerView() let bounds = UIScreen.mainScreen().bounds containerView!.insertSubview(toViewController.view, belowSubview: fromViewController.view) toViewController.view.alpha = 0.5 let finalFrameForVC = fromViewController.view.frame UIView.animateWithDuration(transitionDuration(transitionContext), animations: { fromViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.height) toViewController.view.alpha = 1.0 }, completion: { finished in transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) }) } 

}

Y entonces

 func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if operation == .Pop { return PopAnimator() } return nil; } 

Tutorial útil https://www.objc.io/issues/5-ios7/view-controller-transitions/

Hace poco estaba tratando de hacer algo similar. Decidí que no me gustaba la animación deslizante del UINavigationController, pero tampoco quería hacer las animaciones que UIView te da como curl o algo por el estilo. Quería hacer un desvanecimiento cruzado entre las vistas cuando presiono o exploto.

El problema involucra el hecho de que la vista está literalmente eliminando la vista o apareciendo una encima de la actual, por lo que un fundido no funciona. La solución a la que llegué implicaba tomar mi nueva vista y agregarla como una subvista a la vista superior actual en la stack de UIViewController. Lo agrego con un alfa de 0, luego hago un fundido cruzado. Cuando la secuencia de animación termina, presiono la vista en la stack sin animarla. Luego volví a la vieja vista superior y limpié todo lo que había cambiado.

Es un poco más complicado que eso, porque tiene los artículos de navegación que debe ajustar para que la transición parezca correcta. Además, si realiza cualquier rotación, debe ajustar el tamaño de los cuadros a medida que agrega las vistas como subvistas para que se muestren correctamente en la pantalla. Aquí está algo del código que utilicé. Subclasé el UINavigationController y anulé los métodos push y pop.

 -(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { UIViewController *currentViewController = [self.viewControllers lastObject]; //if we don't have a current controller, we just do a normal push if(currentViewController == nil) { [super pushViewController:viewController animated:animated]; return; } //if no animation was requested, we can skip the cross fade if(!animation) { [super pushViewController:viewController animated:NO]; return; } //start the cross fade. This is a tricky thing. We basically add the new view //as a subview of the current view, and do a cross fade through alpha values. //then we push the new view on the stack without animating it, so it seemlessly is there. //Finally we remove the new view that was added as a subview to the current view. viewController.view.alpha = 0.0; //we need to hold onto this value, we'll be releasing it later NSString *title = [currentViewController.title retain]; //add the view as a subview of the current view [currentViewController.view addSubview:viewController.view]; [currentViewController.view bringSubviewToFront:viewController.view]; UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem; UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem; NSArray *array = nil; //if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it //so leave it out of the array we are creating to pass as the context. I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well. if(rButtonItem != nil) array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil]; else { array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil]; } //remove the right bar button for our transition [currentViewController.navigationItem setRightBarButtonItem:nil animated:YES]; //remove the left bar button and create a backbarbutton looking item //[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO]; //set the back button UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)]; [currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES]; [viewController.navigationItem setLeftBarButtonItem:backButton animated:NO]; [backButton release]; [currentViewController setTitle:viewController.title]; [UIView beginAnimations:@"push view" context:array]; [UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)]; [UIView setAnimationDelegate:self]; [UIView setAnimationDuration:0.80]; [viewController.view setAlpha: 1.0]; [UIView commitAnimations]; } -(void)animationForCrossFadePushDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { UIViewController *c = [(NSArray*)context objectAtIndex:0]; UIViewController *n = [(NSArray*)context objectAtIndex:1]; NSString *title = [(NSArray *)context objectAtIndex:2]; UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3]; UIBarButtonItem *r = nil; //not all views have a right bar button, if we look for it and it isn't in the context, //we'll crash out and not complete the method, but the program won't crash. //So, we need to check if it is there and skip it if it isn't. if([(NSArray *)context count] == 5) r = [(NSArray *)context objectAtIndex:4]; //Take the new view away from being a subview of the current view so when we go back to it //it won't be there anymore. [[[c.view subviews] lastObject] removeFromSuperview]; [c setTitle:title]; [title release]; //set the search button [c.navigationItem setLeftBarButtonItem:l animated:NO]; //set the next button if(r != nil) [c.navigationItem setRightBarButtonItem:r animated:NO]; [super pushViewController:n animated:NO]; } 

Como menciono en el código, siempre tengo un elemento del botón de la barra izquierda, por lo que no compruebo si es nulo antes de colocarlo en la matriz que paso como el contexto para el delegado de la animación. Si haces esto, es posible que quieras hacer ese control.

El problema que encontré fue que si falla en absoluto en el método delegado, no bloqueará el progtwig. Simplemente impide que el delegado complete, pero no recibe ningún tipo de advertencia.
Así que desde que estaba haciendo mi limpieza en esa rutina de delegado, estaba causando un comportamiento visual extraño ya que no estaba terminando la limpieza.

El botón Atrás que creo llama a un método “goBack”, y ese método solo llama a la rutina pop.

 -(void)goBack { [self popViewControllerAnimated:YES]; } 

Además, aquí está mi rutina pop.

 -(UIViewController *)popViewControllerAnimated:(BOOL)animated { //get the count for the number of viewControllers on the stack int viewCount = [[self viewControllers] count]; //get the top view controller on the stack UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1]; //get the next viewController after the top one (this will be the new top one) UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2]; //if no animation was requested, we can skip the cross fade if(!animated) { [super popViewControllerAnimated:NO]; return topViewController; } //start of the cross fade pop. A bit tricky. We need to add the new top controller //as a subview of the curent view controler with an alpha of 0. We then do a cross fade. //After that we pop the view controller off the stack without animating it. //Then the cleanup happens: if the view that was popped is not released, then we //need to remove the subview we added and change some titles back. newTopViewController.view.alpha = 0.0; [topViewController.view addSubview:newTopViewController.view]; [topViewController.view bringSubviewToFront:newTopViewController.view]; NSString *title = [topViewController.title retain]; UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem; UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem; //set the new buttons on top of the current controller from the new top controller if(newTopViewController.navigationItem.leftBarButtonItem != nil) { [topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES]; } if(newTopViewController.navigationItem.rightBarButtonItem != nil) { [topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES]; } [topViewController setTitle:newTopViewController.title]; //[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title]; NSArray *array = nil; if(rButtonItem != nil) array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil]; else { array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil]; } [UIView beginAnimations:@"pop view" context:array]; [UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)]; [UIView setAnimationDelegate:self]; [UIView setAnimationDuration:0.80]; [newTopViewController.view setAlpha: 1.0]; [UIView commitAnimations]; return topViewController; } -(void)animationForCrossFadePopDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { UIViewController *c = [(NSArray *)context objectAtIndex:0]; //UIViewController *n = [(NSArray *)context objectAtIndex:1]; NSString *title = [(NSArray *)context objectAtIndex:1]; UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2]; UIBarButtonItem *r = nil; //Not all views have a right bar button. If we look for one that isn't there // we'll crash out and not complete this method, but the program will continue. //So we need to check if it is therea nd skip it if it isn't. if([(NSArray *)context count] == 4) r = [(NSArray *)context objectAtIndex:3]; //pop the current view from the stack without animation [super popViewControllerAnimated:NO]; //if what was the current veiw controller is not nil, then lets correct the changes //we made to it. if(c != nil) { //remove the subview we added for the transition [[c.view.subviews lastObject] removeFromSuperview]; //reset the title we changed c.title = title; [title release]; //replace the left bar button that we changed [c.navigationItem setLeftBarButtonItem:l animated:NO]; //if we were passed a right bar button item, replace that one as well if(r != nil) [c.navigationItem setRightBarButtonItem:r animated:NO]; else { [c.navigationItem setRightBarButtonItem:nil animated:NO]; } } } 

Eso es practicamente todo. Necesitará un código adicional si desea implementar rotaciones. Deberá establecer el tamaño del marco de las vistas que agregue como subvistas antes de mostrarlas; de lo contrario, se encontrará con problemas de orientación horizontal, pero la última vez que vio la vista anterior fue vertical. Entonces, lo agrega como una vista secundaria y lo desvanece, pero se muestra como retrato, luego cuando aparece sin animación, la misma vista, pero la que está en la stack, ahora es horizontal. Todo parece un poco raro. La implementación de rotación de todos es un poco diferente, así que no incluí mi código aquí.

Espero que ayude a algunas personas. He buscado algo así y no he podido encontrar nada. No creo que esta sea la respuesta perfecta, pero a esta altura me está yendo muy bien.

Así es como he hecho lo mismo en Swift:

Para Push:

  UIView.animateWithDuration(0.75, animations: { () -> Void in UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut) self.navigationController!.pushViewController(nextView, animated: false) UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false) }) 

Para Pop:

De hecho, hice esto de forma un poco diferente a algunas de las respuestas anteriores, pero como soy nuevo en el desarrollo de Swift, puede que no sea lo correcto. He reemplazado viewWillDisappear:animated: y agregué el código pop allí:

  UIView.animateWithDuration(0.75, animations: { () -> Void in UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut) UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false) }) super.viewWillDisappear(animated) 

Usando la respuesta de iJordan como inspiración, ¿por qué no simplemente crear una Categoría en UINavigationController para usar en toda su aplicación en lugar de copiar / pegar este código de animación por todas partes?

UINavigationController + Animation.h

 @interface UINavigationController (Animation) - (void) pushViewControllerWithFlip:(UIViewController*) controller; - (void) popViewControllerWithFlip; @end 

UINavigationController + Animation.m

 @implementation UINavigationController (Animation) - (void) pushViewControllerWithFlip:(UIViewController *) controller { [UIView animateWithDuration:0.50 animations:^{ [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [self pushViewController:controller animated:NO]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO]; }]; } - (void) popViewControllerWithFlip { [UIView animateWithDuration:0.5 animations:^{ [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO]; }]; [self popViewControllerAnimated:NO]; } @end 

Then simply import the UINavigationController+Animation.h file and call it normally:

 [self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]]; [self.navigationController popViewControllerWithFlip]; 

Have a look at ADTransitionController , a drop in replacement for UINavigationController with custom transition animations (its API matches the API of UINavigationController) that we created at Applidium.

You can use different pre-defined animations for push and pop actions such as Swipe , Fade , Cube , Carrousel , Zoom and so on.

While all the answers here are great and most work very well, there is a slightly simpler method which achieves the same effect…

For Push:

  NextViewController *nextViewController = [[NextViewController alloc] init]; // Shift the view to take the status bar into account CGRect frame = nextViewController.view.frame; frame.origin.y -= 20; frame.size.height += 20; nextViewController.view.frame = frame; [UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) { [self.navigationController pushViewController:nextViewController animated:NO]; }]; 

For Pop:

  int numViewControllers = self.navigationController.viewControllers.count; UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view]; [UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) { [self.navigationController popViewControllerAnimated:NO]; }];} 

You can now use UIView.transition . Note that animated:false . This works with any transition option, pop, push, or stack replace.

 if let nav = self.navigationController { UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: { _ = nav.popViewController(animated:false) }, completion:nil) } 

Es muy sencillo

 self.navigationController?.view.semanticContentAttribute = .forceRightToLeft 

Based on jordanperry answer updated for swift 4

For push UIViewController

 let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController UIView.animate(withDuration: 0.75, animations: {() -> Void in UIView.setAnimationCurve(.easeInOut) self.navigationController?.pushViewController(terms, animated: true) UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false) }) 

For Pop

 UIView.animate(withDuration: 0.75, animations: {() -> Void in UIView.setAnimationCurve(.easeInOut) UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false) }) navigationController?.popViewController(animated: false) 

See my answer to this question for a way to do it in far fewer lines of code. This method allows you to animate a pseudo-“Push” of a new view controller any way you like, and when the animation is done it sets up the Navigation Controller just as if you had used the standard Push method. My example lets you animate either a slide-in from the left or from the right. Code repeated here for convenience:

 -(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft { [self addChildViewController:neighbor]; CGRect offscreenFrame = self.view.frame; if(rightToLeft) { offscreenFrame.origin.x = offscreenFrame.size.width * -1.0; } else if(direction == MyClimbDirectionRight) { offscreenFrame.origin.x = offscreenFrame.size.width; } [[neighbor view] setFrame:offscreenFrame]; [self.view addSubview:[neighbor view]]; [neighbor didMoveToParentViewController:self]; [UIView animateWithDuration:0.5 animations:^{ [[neighbor view] setFrame:self.view.frame]; } completion:^(BOOL finished){ [neighbor willMoveToParentViewController:nil]; [neighbor.view removeFromSuperview]; [neighbor removeFromParentViewController]; [[self navigationController] pushViewController:neighbor animated:NO]; NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy]; [newStack removeObjectAtIndex:1]; //self, just below top [[self navigationController] setViewControllers:newStack]; }]; } 

I am not aware of any way you can change the transition animation publicly.

If the “back” button is not necessary you should use modal view controllers to have the “push from bottom” / “flip” / “fade” / (≥3.2)”page curl” transitions.


On the private side, the method -pushViewController:animated: calls the undocumented method -pushViewController:transition:forceImmediate: , so eg if you want a flip-from-left-to-right transition, you can use

 [navCtrler pushViewController:ctrler transition:10 forceImmediate:NO]; 

You can’t change the “pop” transition this way, however.

From the sample app, check out this variation. https://github.com/mpospese/MPFoldTransition/

 #pragma mark - UINavigationController(MPFoldTransition) @implementation UINavigationController(MPFoldTransition) //- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated - (void)pushViewController:(UIViewController *)viewController foldStyle:(MPFoldStyle)style { [MPFoldTransition transitionFromViewController:[self visibleViewController] toViewController:viewController duration:[MPFoldTransition defaultDuration] style:style completion:^(BOOL finished) { [self pushViewController:viewController animated:NO]; } ]; } - (UIViewController *)popViewControllerWithFoldStyle:(MPFoldStyle)style { UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2]; [MPFoldTransition transitionFromViewController:[self visibleViewController] toViewController:toController duration:[MPFoldTransition defaultDuration] style:style completion:^(BOOL finished) { [self popViewControllerAnimated:NO]; } ]; return toController; } 

Solo usa:

 ViewController *viewController = [[ViewController alloc] init]; UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; navController.navigationBarHidden = YES; [self presentViewController:navController animated:YES completion: nil]; [viewController release]; [navController release]; 

Realising this is an old question. I still would like to post this answer, as I had some problems popping several viewControllers with the proposed answers. My solution is to subclass UINavigationController and override all the pop and push methods.

FlippingNavigationController.h

 @interface FlippingNavigationController : UINavigationController @end 

FlippingNavigationController.m:

 #import "FlippingNavigationController.h" #define FLIP_DURATION 0.5 @implementation FlippingNavigationController - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { [UIView transitionWithView:self.view duration:animated?FLIP_DURATION:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight animations:^{ [super pushViewController:viewController animated:NO]; } completion:nil]; } - (UIViewController *)popViewControllerAnimated:(BOOL)animated { return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]] animated:animated] lastObject]; } - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated { return [self popToViewController:[self.viewControllers firstObject] animated:animated]; } - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated { __block NSArray* viewControllers = nil; [UIView transitionWithView:self.view duration:animated?FLIP_DURATION:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft animations:^{ viewControllers = [super popToViewController:viewController animated:NO]; } completion:nil]; return viewControllers; } @end 

I found a mildly recursive way to do this that works for my purposes. I have an instance variable BOOL that I use to block the normal popping animation and substitute my own non-animated pop message. The variable is initially set to NO. When the back button is tapped, the delegate method sets it to YES and sends a new non-animated pop message to the nav bar, thereby calling the same delegate method again, this time with the variable set to YES. With the variable is set to YES, the delegate method sets it to NO and returns YES to allow the non-animated pop occur. After the second delegate call returns, we end up back in the first one, where NO is returned, blocking the original animated pop! It’s actually not as messy as it sounds. My shouldPopItem method looks like this:

 - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item { if ([[navigationBar items] indexOfObject:item] == 1) { [expandedStack restack]; } if (!progPop) { progPop = YES; [navBar popNavigationItemAnimated:NO]; return NO; } else { progPop = NO; return YES; } } 

Funciona para mi.