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


221

Tengo una aplicación basada en 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 vuelto a elegir la respuesta a lo que creo que es el más relevante ahora. Si hay alguien que piense lo contrario, hágamelo saber en los comentarios


25
A partir de iOS 7, hay una API oficial para esto; vea el soporte de animación de transición personalizada de UINavigationControllerDelegate . También hay un video de WWDC 2013 sobre esto.
Jesse Rusak

He agregado una respuesta (a continuación) para hacer esto en Swift: me encontré con esta pregunta preguntando sobre las implementaciones de Swift, así que pensé en intervenir en mi implementación posterior.
djbp

1
Para un muy buen tutorial con la API oficial (iOS 7+), ver: bradbambara.wordpress.com/2014/04/11/…
nikolovski

1
@JesseRusak enlace actualizado a WWDC 2013 Video: developer.apple.com/videos/play/wwdc2013-218
Wojciech Rutkowski

1
Cambié mi respuesta aceptada chicos y chicas. ¡Espero que esto ayude! GLHF
Jab

Respuestas:


35

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

Para 2019, la "respuesta final".

Preámbulo:

Digamos que eres nuevo en el desarrollo de iOS. Confusamente, Apple proporciona dos transiciones que se pueden usar fácilmente. Estos son: "fundido cruzado" y "voltear".

Pero, por supuesto, "fundido cruzado" y "flip" son inútiles. Nunca se usan. ¡Nadie sabe por qué Apple proporcionó esas dos transiciones inútiles!

Entonces:

Digamos que desea hacer una transición común y corriente, como "diapositiva". En ese caso, ¡tienes que hacer una ENORME cantidad de trabajo! .

Ese trabajo, se explica en este post.

Solo para repetir:

Sorprendentemente: con iOS, si desea las transiciones cotidianas más simples, más comunes (como una diapositiva normal), debe realizar todo el trabajo de implementar una transición personalizada completa .

He aquí cómo hacerlo ...

1. Necesitas una costumbre UIViewControllerAnimatedTransitioning

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

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

  3. De hecho, debe escribir dos rutinas diferentes para el interior animateTransition. Uno para el empuje y otro para el pop. Probablemente los nombre animatePushy animatePop. En el interior animateTransition, simplemente bifurca popStylea las dos rutinas

  4. El siguiente ejemplo hace un simple movimiento de desplazamiento / desplazamiento

  5. En tu animatePushy animatePoprutinas. Usted debe conseguir la "vista desde" y "a la vista". (Cómo hacerlo, se muestra en el ejemplo de código).

  6. y debe hacerlo addSubview para la nueva vista "a".

  7. y debes llamar completeTransitional final de tu anime

Entonces ..

  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.

Nota: curiosamente, solo tiene que hacer esto en el "primer" controlador de vista. (El que está "debajo").

Con el que aparece en la parte superior , no haga nada . Fácil.

Entonces 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 reviente exactamente como de costumbre, sin cambios. Para empujar ...

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

y para hacer estallar, puedes hacerlo si lo deseas en la siguiente pantalla:

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

Uf.


3. También disfrute las otras respuestas en esta página que explican cómo anular AnimatedTransition

¡Desplácese a @AlanZeino y @elias responden para más discusión sobre cómo usar las AnimatedTransitioningaplicaciones iOS en estos días!


¡Excelente! Si quiero que el gesto de desplazamiento hacia atrás de navegación también sea compatible con el mismo AnimatedTransition. ¿Alguna idea?
Sam Chi Wen

gracias @samchiwen - de hecho eso es exactamente lo que son animatePushy animatePopson ... ¡las dos direcciones diferentes!
Fattie

268

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)

34
+1, esta es realmente la solución más sensata. Solo una nota menor para futuros visitantes: la Animated:NOparte es vital. Si YESse aprueba, las animaciones se mezclan y causan efectos divertidos.
DarkDust

12
La mejor solución hasta ahora ... Y para los principiantes, no olviden incluir QuartCore (#import <QuartzCore / QuartzCore.h>)
nomann

44
El único problema que tengo con esta solución es que se llama a viewDtrolAppar del controlador empujado inmediatamente después de presionarlo sin animación. ¿Hay alguna forma de evitarlo?
Pedro Mancheno

99
Mi problema con este código es que cada vista parece parpadear en gris o blanco a medida que se deslizan hacia adentro o hacia afuera.
Chris

1
comprobado en iOS 7.1.2 e iOS 8.3: este código funciona bien y también funciona bien para el métodosetViewControllers:
proff

256

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 voy a seguir adelante y actualizarlo para usar bloques de animación, que es la forma recomendada por Apple de 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];

3
Gracias por esto. Pero el pop se realiza automáticamente por el UINavigationController. ¿Cómo anula ese comportamiento para poder llamar a su lógica pop personalizada?
Joshua Frank

1
@stuckj en realidad funciona !!! solo tiene que reemplazar superporself.navigationController
holierthanthou84

¿Hay alguna forma de obtener una diapositiva desde la izquierda en lugar de la diapositiva predeterminada desde la derecha?
cuña

El primero no muestra la nueva vista en absoluto. El segundo no muestra animación. Muy mala respuesta! iOS 7.
Dmitry

2
¿Por qué le has dado a la UIViewControllersubclase un nombre sin la parte "ViewController"? Este nombre es más apropiado para UIView.
user2159978

29

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 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];

19

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:

Puede hacer esto también con Segue, simplemente implemente esto en prepareForSegueo shouldPerformSegueWithIdentifier. Sin embargo , esto también mantendrá la animación predeterminada. Para solucionar esto, 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 hacerlo en una secuencia, las dos últimas líneas deben reemplazarse con:

self.navigationController?.popViewControllerAnimated(false)

Aunque establezco falso, lo ignora un poco.


Cómo eliminar el color negro en el fondo al final de la animación.
Madhu

No funciona para la animación del controlador de vista push funciona para el controlador de vista Pop
Mukul Más

16

¡Recuerda que en Swift , extension 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)
    }

}

11

Usar llamadas privadas es una mala idea, ya que Apple ya no aprueba las aplicaciones que lo hacen. Tal vez podrías intentar 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];

Solo funciona a la mitad: no resolverá el problema más difícil de las animaciones pop.
Adam

Me gusta más esta solución, y sí, funciona. El uso de métodos privados lo rechazará con seguridad.
Benjamin Intal

@nicktmro, que es la llamada de API privada. No noté ninguno.
Franklin

@Franklin hubo una discusión aquí hace un tiempo sobre el uso, lo -pushViewController:transition:forceImmediate:que sería una mala idea.
nicktmro

9

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

Es bastante simple combinar esto con la forma en que se UINavigationControlleranima entre dos controladores de vista si crea una subclase de UINavigationControllery devuelve una instancia de una clase que se ajusta al UIViewControllerAnimatedTransitioningprotocolo.

Por ejemplo, aquí está mi UINavigationControllersubclase:

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é el UINavigationControllerDelegatemismo y, en una extensión de mi subclase, implemento el método UINavigationControllerDelegateque le permite devolver un controlador de animación personalizado (es decir, NavigationControllerAnimation). Este controlador de animación personalizado reemplazará la animación original por usted.

Probablemente se esté preguntando por qué paso la operación a la NavigationControllerAnimationinstancia a través de su inicializador. Hago esto para que en NavigationControllerAnimationla implementación de laUIViewControllerAnimatedTransitioning protocolo 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 UIViewControllerAnimatedTransitioningprotocolo 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 de ida y vuelta serán diferentes. Cuando está en una operación de inserción, el controlador para ver será el que se está presionando. Cuando está en una operación emergente, el controlador de vista será el que se está haciendo la transición, y el controlador de vista será el que se está haciendo estallar.

Además, el tocontrolador de vista debe agregarse como una subvista del containerViewcontexto de transición.

Cuando finalice su animación, debe llamar transitionContext.completeTransition(true). Si está haciendo una transición interactiva, tendrá que devolver dinámicamente un Boola 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, por lo que no diría que es un gran código de animación, pero aún 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 'siguiente página sobre la parte superior', quería implementar una animación 1: 1 del antiguo controlador de vista al mismo tiempo que la nueva vista aparece el controlador 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 establecer el toViewControllerorigen de la vista en el eje x fuera de la pantalla, agregarlo como la subvista del containerView, animándolo en la pantalla al ponerlo origin.xa cero. Al mismo tiempo, animé la fromViewControllervista de distancia al configurarla origin.xfuera 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 el toViewControllercomo una subvista del containerView, y anime fromViewControllera la derecha como anima toViewControllerdesde 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 un resumen de todo el archivo swift:

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


¡Excelente!. Lo que quería hacer es SOLO animar en dirección opuesta. Examiné algunas otras soluciones, pero todas exhiben parpadeo en la pantalla izquierda y derecha. Parece que la animación implícita de cambio alfa no se puede eliminar con ellos. Solo esta solución solucionó el problema.
beshio

Sí, esta es la única solución moderna correcta. (No me importa, ¡pero es exactamente la misma que la solución que escribí a continuación! :))
Fattie

@AlanZeino ¿Qué sucede si dentro del mismo ViewController necesita tener animaciones diferentes para hacer clic en un botón diferente? Entonces, para el botón 1 necesita una animación de disolución, para el botón 2 necesita la transición predeterminada.
jzeferino

7

Hay UINavigationControllerDelegate y UIViewControllerAnimatedTransition allí, puedes cambiar la animación para lo que quieras.

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/


6

Basado en la respuesta actualizada para swift 4jordanperry

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)

5

Así es como 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 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 agregado 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)

5

La respuesta de @Luca Davanzo en Swift 4.2

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: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewController(animated: 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: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        transition.type = type
        self.view.layer.add(transition, forKey: nil)
    }

}

4

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 así. Quería hacer un fundido cruzado entre las vistas cuando presiono o hago estallar.

El problema allí implica el hecho de que la vista está literalmente eliminando la vista o colocando una sobre la parte superior de la actual, por lo que un fundido no funciona. La solución a la que llegué consistió en tomar mi nueva vista y agregarla como una subvista a la vista superior actual en la pila de UIViewController. Lo agrego con un alfa de 0, luego hago un fundido cruzado. 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 tiene los elementos de navegación que tiene que ajustar para que la transición se vea correcta. Además, si realiza alguna 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í hay algunos de los códigos que usé. Subclasifiqué 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 de botón de barra izquierda, así que no verifico si es nulo antes de ponerlo en la matriz que paso como contexto para el delegado de animación. Si hace esto, es posible que desee hacer esa verificación.

El problema que encontré fue que si falla en el método delegado, no bloqueará el programa. Simplemente evita que el delegado se complete pero no recibe ningún tipo de advertencia.
Entonces, como 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. Tendrá que establecer el tamaño de marco de sus vistas que agregue como subvistas antes de mostrarlas; de lo contrario, se encontrará con problemas, la orientación es horizontal, pero la última vez que vio la vista anterior fue vertical. Entonces, lo agrega como una vista secundaria y se desvanece, pero aparece como retrato, luego, cuando aparecemos sin animación, la misma vista, pero la que está en la pila, 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 para eso aquí.

Espero que ayude a algunas personas. He buscado algo como esto y no he podido encontrar nada. No creo que esta sea la respuesta perfecta, pero en este momento me está funcionando muy bien.


Aunque admirable, ¡esta no es la solución honestamente ahora 7 años después!
Fattie

Tienes razón. Esta respuesta fue de 2011. Funcionó en aquel entonces, pero las cosas han cambiado mucho desde entonces. =)
georryan

4

Ahora puedes usar UIView.transition. Tenga en cuenta que animated:false. Esto funciona con cualquier opción de transición, pop, push o reemplazo de pila.

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

1
@Fattie, este método en particular solo funciona con cualquiera de las animaciones estándar, tales como volteretas y rizos listados en developer.apple.com/documentation/uikit/uiviewanimationoptions
Peter DeWeese

3

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 en todo el lugar?

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];

Inteligente. Pero, ¿por qué no agregar métodos push / pop que toman un argumento de UIViewAnimationTransition en lugar de hardcode para flipFromRight?
Jef

@Jef estos son métodos convenientes: de esta manera, el implementador no necesita recordar qué valor de UIViewAnimationTransition pasar para cada tipo de animación específico, simplemente llaman al método con el nombre "inglés" de lo que quieren lograr.
DiscDev

@Jef también, su sugerencia es definitivamente válida: si todavía usara el objetivo-c y necesitara admitir muchos estilos de transición (definitivamente no se recomienda ya que muchos estilos de transición diferentes confundirán a los usuarios) Tendría 1 método que toma el tipo UIViewAnimationTransition, entonces Varios métodos de conveniencia para facilitar el desarrollo.
DiscDev

3

Es muy simple

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft

Bienvenido a StackOverflow: si publica código, XML o muestras de datos, resalte esas líneas en el editor de texto y haga clic en el botón "muestras de código" ({}) en la barra de herramientas del editor o use Ctrl + K en su teclado para formatear bien y sintaxis resaltarlo!
WhatsThePoint

2

Eche un vistazo a ADTransitionController , un reemplazo sustitutivo de 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 push y pop como Swipe , Fade , Cube , Carrousel , Zoom , etc.


2

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];
  }];}

Esto se bloqueará cuando aparezca en el controlador de vista raíz.
Abdullah Umer

1

No conozco ninguna forma de cambiar públicamente la animación de transición.

Si el botón "atrás" no es necesario, debe usar controladores de vista modal para que las transiciones "empujar desde abajo" / "voltear" / "desvanecerse" / (≥3.2) "curvar 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 puede cambiar la transición "pop" de esta manera.


1

Vea mi respuesta a esta pregunta para saber cómo hacerlo en muchas menos líneas de código. Este método le permite animar un pseudo- "Empuje" de un nuevo controlador de vista de la manera que desee, y cuando se realiza la animación, configura el Controlador de navegación como si hubiera utilizado el método Push estándar. Mi ejemplo le permite animar un deslizamiento 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];
    }];
}

0

Desde la aplicación de muestra, mira 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;
}

0

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];

0

Darse cuenta de esto es una vieja pregunta. Todavía me gustaría publicar esta respuesta, ya que tuve algunos problemas para mostrar varias viewControllersde las respuestas propuestas. Mi solución es subclasificar UINavigationControllery 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

0

Sé que este hilo es viejo, pero pensé en poner mis dos centavos. No necesita hacer una animación personalizada, hay una manera simple (quizás hacky) de hacerlo. En lugar de usar push, cree un nuevo controlador de navegación, haga que el nuevo controlador de vista sea el controlador de vista raíz de ese controlador de navegación, y luego presente el controlador de navegación desde el controlador de navegación original. El presente es fácilmente personalizable con muchos estilos, y no es necesario hacer una animación personalizada.

Por ejemplo:

UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)

Y puedes cambiar el estilo de presentación como quieras.


-1

Encontré una forma ligeramente recursiva de hacer esto que funciona para mis propósitos. Tengo una variable de instancia BOOL que uso para bloquear la animación emergente normal y sustituir mi propio mensaje pop no animado. La variable se establece inicialmente en NO. Cuando se toca el botón Atrás, el método delegado lo establece en SÍ y envía un nuevo mensaje emergente no animado a la barra de navegación, llamando así al mismo método delegado nuevamente, 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 ocurra el pop no animado. Después de que regresa la segunda llamada de delegado, terminamos nuevamente en la primera, donde NO se devuelve, ¡bloqueando el pop animado original! En realidad no es tan desordenado como parece. Mi método shouldPopItem se ve así:

- (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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.