ios - modelar - apps para animar en ipad
Cómo cambiar las animaciones Push y Pop en una aplicación basada en navegación (23)
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?
Editar 2018
Ha habido muchas respuestas a esta pregunta y ha pasado bastante tiempo, he reelegido la respuesta a lo que creo que es la más relevante ahora. Si hay alguien que piense lo contrario, hágamelo saber en los comentarios.
Cómo cambiar las animaciones Push y Pop en una aplicación basada en navegación ...
Para 2018, la "respuesta final!"
Preámbulo Digamos que eres nuevo en el desarrollo de iOS, tal vez de Android. Muy confusamente, Apple proporciona dos (2) transiciones que pueden usarse fácilmente. Estos son: "crossfade" y "flip". Ahora, las dos transiciones más inútiles en toda la computación son "crossfade" y "flip", que nadie usa. Si desea hacer las transiciones más comunes, como "deslizarse", tiene que hacer una GRAN cantidad de trabajo. Ese trabajo, se explica en este post!
En primer lugar, si desea usar uno de los dos animes que Apple proporciona (crossfade, voltea), es simple: use la solución de @PeterDeWeese arriba.
En segundo lugar, está el viejo quickfix de transición rápido y sucio de CAT. Eso se explica en detalle aquí . Realmente no funciona, y no es una solución realista.
De lo contrario, sorprendentemente, tiene que hacer el esfuerzo de hacer una transición personalizada completa .
Repetir:
incluso si solo desea la transición más simple, más común, de traslado / traslado , para bien o para mal, debe implementar una transición personalizada completa.
Aquí está cómo hacerlo ...
1. Necesita un UIViewControllerAnimatedTransitioning
personalizado
Necesitas un bool tuyo como
popStyle
. (¿Se está activando, o está saliendo?)Debes incluir
transitionDuration
(trivial) y la llamada principal,animateTransition
De hecho, tendrá que escribir dos rutinas diferentes para dentro de
animateTransition
. Una para el empuje, y otra para el pop. Probablemente losanimatePush
yanimatePop
. Dentro deanimateTransition
, simplementepopStyle
enpopStyle
a las dos rutinasEl siguiente ejemplo hace un simple movimiento sobre / movimiento
En tus rutinas
animatePush
yanimatePop
. Debe obtener el "desde la vista" y el "para ver". (Cómo se muestra en el ejemplo).y lo más importante que debe hacer es, de hecho,
addSubview
para la nueva vista "para".Debes llamar
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. Úsalo en tu controlador de vista.
Tenga en cuenta que solo tiene que hacer esto en el "primer" controlador de vista.
El que destapas, no haces nada. Fácil.
Así que tu clase ...
class SomeScreen: UIViewController {
}
se convierte en ...
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 haga estallar exactamente como normal, 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 si lo desea, simplemente haga eso en la siguiente pantalla:
class NextScreen: TotallyOrdinaryUIViewController {
@IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
navigationController?.popViewController(animated: true)
}
}
Es "así de simple": O
Aquí es cómo he hecho lo mismo en Swift:
Para empujar
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 el pop:
De hecho, hice esto de manera un poco diferente a algunas de las respuestas anteriores, pero como soy nuevo en el desarrollo de Swift, puede que no sea correcto. He anulado 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)
Así es como siempre he logrado completar esta tarea.
Para empujar
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 el 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 seguiré adelante y lo actualizaré para usar bloques de animación, que es la forma recomendada por Apple para hacer animaciones de todos modos.
Para empujar
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 el pop:
[UIView animateWithDuration:0.75
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
}];
[self.navigationController popViewControllerAnimated:NO];
Basado en la answer actualizada para swift 4
Para empujar 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)
})
Para el 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)
Dado que este es el mejor resultado en Google, pensé que compartiría lo que creo que es la forma más sensata; que es utilizar la API de transición iOS 7+. Implementé esto para iOS 10 con Swift 3.
Es bastante simple combinar esto con la forma en que UINavigationController
anima entre dos controladores de vista si creas una subclase de UINavigationController
y devuelves una instancia de una clase que se ajusta 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 configuré UINavigationControllerDelegate
a sí mismo y, en una extensión de 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 valores para 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
sepa 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 según la operación.
El resto es bastante estándar. Implemente las dos funciones requeridas en el protocolo UIViewControllerAnimatedTransitioning
y anime como quiera:
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 hacia y desde serán diferentes. Cuando está en una operación de empuje, el controlador de vista será el que se está presionando. Cuando se está realizando una operación emergente, el controlador de vista será el que se está cambiando, y el controlador de vista será el que se abrirá.
Además, to
ver el controlador se debe agregar como una subvista del containerView
en el contexto de transición.
Cuando se complete la animación, debe llamar a la instalación de transitionContext.completeTransition(true)
contexto transitionContext.completeTransition(true)
. Si está haciendo una transición interactiva, tendrá que devolver dinámicamente un Bool
a 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 intrépido y lo escribí bastante rápido, así que no diría que es un gran código de animación, pero aún muestra cómo hacer la parte de la animación.
La mía fue una transición realmente simple; Quería imitar la misma animación que suele hacer UINavigationController, pero en lugar de la animación de "página siguiente", quise implementar una animación 1: 1 del controlador de vista anterior al mismo tiempo que la nueva vista Aparece el controlador. Esto tiene el efecto de hacer que los dos controladores de vista parezcan estar pegados entre sí.
Para la operación de inserción, eso requiere configurar primero el origen de la vista de toViewController
en el eje x fuera de la pantalla, agregarlo como la subvista del containerView
, animarlo en la pantalla configurando ese origin.x
en cero. Al mismo tiempo, fromViewController
la vista de fromViewController
alejando la configuración de origin.x
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 lo inverso. Agregue el toViewController
como una subvista del containerView
y anime 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 swift:
https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be
Hay UINavigationControllerDelegate y UIViewControllerAnimatedTransitioning allí puedes cambiar la animación para lo que quieras.
Por ejemplo, esta es una animación vertical 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/
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 empujar.
Versión 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)
Recientemente estuve tratando de hacer algo similar. Decidí que no me gustaba la animación deslizante de UINavigationController, pero tampoco quería hacer las animaciones que UIView te da como rizos o algo así. Quería hacer un fundido cruzado entre las vistas cuando presiono o abrí.
El problema allí involucra el hecho de que la vista está literalmente eliminando la vista o haciendo estallar una sobre la actual, por lo que un desvanecimiento no funciona. La solución a la que llegué implicó tomar mi nueva vista y agregarla como una subvista a la vista superior actual en la pila del UIViewController. Lo agrego con un alfa de 0, luego hago un crossfade. Cuando finaliza la secuencia de animación, empujo la vista a la pila sin animarla. Luego vuelvo al viejo topView y limpio las cosas que había cambiado.
Es un poco más complicado que eso, porque tienes los elementos de navegación que debes ajustar para que la transición se vea correcta. Además, si realiza alguna rotación, deberá ajustar el tamaño de los marcos a medida que agrega las vistas como subvistas para que se muestren correctamente en la pantalla. Aquí está algo del código que utilicé. Subcomité 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, así que no verifico si es nulo antes de colocarlo en la matriz que paso como contexto para el delegado de animación. Si hace esto, puede querer hacer ese cheque.
El problema que encontré fue que si falla en absoluto en el método de delegado, no se bloqueará el programa. Simplemente impide que el delegado complete, pero no recibe ningún tipo de advertencia.
Así que ya 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 crea un método "goBack", y ese método simplemente llama a la rutina emergente.
-(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á algún código adicional si desea implementar rotaciones. Deberá establecer el tamaño de marco de sus 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 se difumina, pero se muestra como retrato, luego, cuando aparece sin animación, la misma vista, pero la que está en la pila, ahora es paisaje. Todo el asunto se ve un poco raro. La implementación de la rotación de todos es un poco diferente, así que no incluí mi código para eso aquí.
Espero que ayude a algunas personas. He buscado por todas partes algo como esto y no pude encontrar nada. No creo que esta sea la respuesta perfecta, pero está funcionando muy bien para mí en este momento.
Recuerda que en Swift , la extensión son 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)
}
}
Respuesta de @Magnus, 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:
También puede hacer esto con Segue, solo implemente esto en prepareForSegue
o shouldPerformSegueWithIdentifier
. Sin embargo , esto mantendrá la animación predeterminada también. Para solucionar este problema, debe ir al guión gráfico, hacer clic en Segue y desmarcar la casilla ''Animar''. Pero esto limitará tu aplicación para iOS 9.0 y superior (al menos cuando lo hice en Xcode 7).
Al hacer un segue, las dos últimas líneas deben reemplazarse con:
self.navigationController?.popViewControllerAnimated(false)
A pesar de que lo puse en falso, lo ignora.
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];
Usar llamadas privadas es una mala idea, ya que Apple ya no aprueba las aplicaciones que lo hacen. 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];
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];
Es muy sencillo
self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
No tengo conocimiento de ninguna manera en que pueda cambiar la animación de transición públicamente.
Si el botón "atrás" no es necesario, debe usar los controladores de vista modal para que el "empuje desde abajo" / "voltear" / "desvanecerse" / (≥3.2) las "transiciones de página".
En el lado privado , el método -pushViewController:animated:
llama al método no documentado -pushViewController:transition:forceImmediate:
, por lo que, por ejemplo, si desea una transición de giro de izquierda a derecha, puede usar
[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];
Sin embargo, no puedes cambiar la transición "pop" de esta manera.
Si bien todas las respuestas aquí son excelentes y la mayoría funciona muy bien, hay un método un poco más simple que logra el mismo efecto ...
Para empujar
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];
}];
Para el 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];
}];}
Usando la respuesta de iJordan como inspiración, ¿por qué no simplemente crea una Categoría en UINavigationController para usarla en toda la 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
Luego simplemente importe el archivo UINavigationController + Animation.h y llámelo normalmente:
[self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];
[self.navigationController popViewControllerWithFlip];
Ahora puedes usar UIView.transition
. Tenga en cuenta que animated:false
. Esto funciona con cualquier opción de transición, pop, push o stack stack.
if let nav = self.navigationController
{
UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: {
_ = nav.popViewController(animated:false)
}, completion:nil)
}
Darse cuenta de esto es una vieja pregunta. Todavía me gustaría publicar esta respuesta, ya que tuve algunos problemas para hacer varios viewControllers
con las respuestas propuestas. Mi solución es crear subclases UINavigationController
y anular todos los métodos pop y push.
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
Desde la aplicación de ejemplo, echa un vistazo a esta variación. 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;
}
Eche un vistazo a ADTransitionController , un reemplazo del UINavigationController con animaciones de transición personalizadas (su API coincide con la API de UINavigationController) que creamos en Applidium.
Puede usar diferentes animaciones predefinidas para acciones de inserción y pop como Swipe , Fade , Cube , Carrousel , Zoom , etc.
Encontré una forma ligeramente recursiva de hacer esto que funciona para mis propósitos. Tengo una variable de instancia BOOL que utilizo para bloquear la animación emergente normal y sustituir mi propio mensaje emergente no animado. La variable se establece inicialmente en NO. Cuando se toca el botón Atrás, el método de delegado lo establece en SÍ y envía un nuevo mensaje emergente no animado a la barra de navegación, por lo que vuelve a llamar al mismo método de delegado, esta vez con la variable establecida en SÍ. Con la variable establecida en SÍ, el método delegado la establece en NO y devuelve SÍ para permitir que se produzca el pop no animado. Después de que regresa la segunda llamada de delegado, terminamos de nuevo en la primera, donde NO se devuelve, ¡bloqueando el pop animado original! En realidad no es tan desordenado como suena. Mi método shouldPopItem tiene este aspecto:
- (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.
Vea mi respuesta a esta pregunta para una manera de hacerlo en muchas menos líneas de código. Este método le permite animar un pseudo "Push" de un nuevo controlador de vista de la forma que desee, y cuando termina la animación, configura el controlador de navegación como si hubiera usado el método Push estándar. Mi ejemplo le permite animar una entrada desde la izquierda o desde la derecha. Código repetido aquí por conveniencia:
-(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];
}];
}