Animación de transición del conmutador RootViewController

125

¿Hay alguna forma de tener un efecto de transición / animación mientras se reemplaza un viewcontroller existente como rootviewcontroller por uno nuevo en appDelegate?

Jefferson
fuente

Respuestas:

272

Puede ajustar el cambio de rootViewControlleren un bloque de animación de transición:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newViewController; }
                completion:nil];
Ole Begemann
fuente
55
Hola Ole, probé este enfoque, funcionó parcialmente, la cuestión es que mi aplicación solo permanecerá en modo horizontal, pero al hacer la transición del controlador de vista raíz, el controlador de vista recién presentado se carga en vertical al principio y gira rápidamente al modo horizontal ¿Cómo resolver eso?
Chris Chen
44
Respondí la pregunta de Chris Chen (¡con suerte! ¿Tal vez?) En su pregunta separada aquí: stackoverflow.com/questions/8053832/…
Kalle
1
oye, quiero una transición de inserción en la misma animación ¿puedo lograr eso?
Usuario 1531343
14
He notado algunos problemas con esto, a saber, elementos extraviados / elementos cargados lentamente. Por ejemplo, si no tiene una barra de navegación en la vc raíz existente, entonces anime a una nueva vc que tenga una, la animación se completa Y LUEGO se agrega la barra de navegación. Parece un poco tonto, ¿alguna idea de por qué puede ser esto y qué se puede hacer?
anon_dev1234
1
Descubrí que llamar newViewController.view.layoutIfNeeded()antes del bloque de animación corrige problemas con elementos cargados de manera perezosa.
Whoa
66

Encontré esto y funciona perfectamente:

en su aplicación Delegue:

- (void)changeRootViewController:(UIViewController*)viewController {

    if (!self.window.rootViewController) {
        self.window.rootViewController = viewController;
        return;
    }

    UIView *snapShot = [self.window snapshotViewAfterScreenUpdates:YES];

    [viewController.view addSubview:snapShot];

    self.window.rootViewController = viewController;

    [UIView animateWithDuration:0.5 animations:^{
        snapShot.layer.opacity = 0;
        snapShot.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
    } completion:^(BOOL finished) {
        [snapShot removeFromSuperview];
    }];
}

en tu aplicación

 if (!app) { app = (AppDelegate *)[[UIApplication sharedApplication] delegate]; }
        [app changeRootViewController:newViewController];

créditos:

https://gist.github.com/gimenete/53704124583b5df3b407

Jesús
fuente
¿Esto admite rotación automática de pantalla?
Wingzero
1
Esta solución funcionó mejor en mi caso. Al usar transitionWithView, el nuevo controlador de vista raíz se diseñó correctamente hasta después de que se completó la transición. Este enfoque permite que el nuevo controlador de vista raíz se agregue a la ventana, se presente y luego se transite.
Fostah
@Wingzero un poco tarde, pero esto permitiría cualquier tipo de transición, ya sea a través de UIView.animations (por ejemplo, un CGAffineTransform con rotate) o un CAAnimation personalizado.
Puede el
41

Estoy publicando la respuesta de Jesús implementada en Swift. Toma el identificador de viewcontroller como argumento, se carga desde el storyboard deseado ViewController y cambia rootViewController con animación.

Actualización de Swift 3.0:

  func changeRootViewController(with identifier:String!) {
    let storyboard = self.window?.rootViewController?.storyboard
    let desiredViewController = storyboard?.instantiateViewController(withIdentifier: identifier);

    let snapshot:UIView = (self.window?.snapshotView(afterScreenUpdates: true))!
    desiredViewController?.view.addSubview(snapshot);

    self.window?.rootViewController = desiredViewController;

    UIView.animate(withDuration: 0.3, animations: {() in
      snapshot.layer.opacity = 0;
      snapshot.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
      }, completion: {
        (value: Bool) in
        snapshot.removeFromSuperview();
    });
  }

Actualización de Swift 2.2:

  func changeRootViewControllerWithIdentifier(identifier:String!) {
    let storyboard = self.window?.rootViewController?.storyboard
    let desiredViewController = storyboard?.instantiateViewControllerWithIdentifier(identifier);

    let snapshot:UIView = (self.window?.snapshotViewAfterScreenUpdates(true))!
    desiredViewController?.view.addSubview(snapshot);

    self.window?.rootViewController = desiredViewController;

    UIView.animateWithDuration(0.3, animations: {() in
      snapshot.layer.opacity = 0;
      snapshot.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
      }, completion: {
        (value: Bool) in
        snapshot.removeFromSuperview();
    });
  }

  class func sharedAppDelegate() -> AppDelegate? {
    return UIApplication.sharedApplication().delegate as? AppDelegate;
  }

Después, tiene un uso muy simple desde cualquier lugar:

let appDelegate = AppDelegate.sharedAppDelegate()
appDelegate?.changeRootViewControllerWithIdentifier("YourViewControllerID")

Actualización de Swift 3.0

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.changeRootViewController(with: "listenViewController")
Neil Galiaskarov
fuente
25

Swift 2

UIView.transitionWithView(self.window!, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromLeft, animations: {
  self.window?.rootViewController = anyViewController
}, completion: nil)

Swift 3, 4, 5

UIView.transition(with: self.window!, duration: 0.5, options: UIView.AnimationOptions.transitionFlipFromLeft, animations: {
  self.window?.rootViewController = anyViewController
}, completion: nil)
Chandrashekhar HM
fuente
XCode arregló mi código de esta manera: `` `UIView.transition (con: self.view.window !, duración: 0.5, opciones: UIViewAnimationOptions.transitionFlipFromTop, animaciones: {appDelegate.window? .RootViewController = myViewController}, finalización: nulo) `` `
scaryguy
10

solo prueba esto. Funciona bien para mi.

BOOL oldState = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:NO];
self.window.rootViewController = viewController;
[UIView transitionWithView:self.window duration:0.5 options:transition animations:^{
    //
} completion:^(BOOL finished) {
    [UIView setAnimationsEnabled:oldState];
}];

EDITAR:

Éste es mejor.

- (void)setRootViewController:(UIViewController *)viewController
               withTransition:(UIViewAnimationOptions)transition
                   completion:(void (^)(BOOL finished))completion {
    UIViewController *oldViewController = self.window.rootViewController;
    [UIView transitionFromView:oldViewController.view 
                        toView:viewController.view
                      duration:0.5f
                       options:(UIViewAnimationOptions)(transition|UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviews)
                    completion:^(BOOL finished) {
        self.window.rootViewController = viewController;
        if (completion) {
            completion(finished);
        }
    }];
}
Dmitry Coolerov
fuente
Tuve una extraña animación predeterminada cuando simplemente cambié el VC raíz. La primera versión se deshizo de eso por mí.
juhan_h
La segunda versión animará el diseño de la subvista, como lo menciona juhan_h. Si esto no es necesario, experimente con la eliminación UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviewso use la primera versión o algún otro método.
ftvs
3

Para no tener problemas con el cambio de transición más adelante en la aplicación, también es bueno borrar la vista anterior de la pila

UIViewController *oldController=self.window.rootViewController;

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{ self.window.rootViewController = nav; }
                completion:^(BOOL finished) {
                    if(oldController!=nil)
                        [oldController.view removeFromSuperview];
                }];
Catalin
fuente
2

La respuesta correcta es que no necesita reemplazar el rootViewControlleren su ventana. En su lugar, cree una personalizada UIViewController, asígnela una vez y deje que muestre un controlador secundario a la vez y reemplácela con animación si es necesario. Puede usar el siguiente código como punto de partida:

Swift 3.0

import Foundation
import UIKit

/// Displays a single child controller at a time.
/// Replaces the current child controller optionally with animation.
class FrameViewController: UIViewController {

    private(set) var displayedViewController: UIViewController?

    func display(_ viewController: UIViewController, animated: Bool = false) {

        addChildViewController(viewController)

        let oldViewController = displayedViewController

        view.addSubview(viewController.view)
        viewController.view.layoutIfNeeded()

        let finishDisplay: (Bool) -> Void = {
            [weak self] finished in
            if !finished { return }
            oldViewController?.view.removeFromSuperview()
            oldViewController?.removeFromParentViewController()
            viewController.didMove(toParentViewController: self)
        }

        if (animated) {
            viewController.view.alpha = 0
            UIView.animate(
                withDuration: 0.5,
                animations: { viewController.view.alpha = 1; oldViewController?.view.alpha = 0 },
                completion: finishDisplay
            )
        }
        else {
            finishDisplay(true)
        }

        displayedViewController = viewController
    }

    override var preferredStatusBarStyle: UIStatusBarStyle {
        return displayedViewController?.preferredStatusBarStyle ?? .default
    }
}

Y la forma en que lo usas es:

...
let rootController = FrameViewController()
rootController.display(UINavigationController(rootViewController: MyController()))
window.rootViewController = rootController
window.makeKeyAndVisible()
...

El ejemplo anterior demuestra que puede anidar en el UINavigationControllerinterior FrameViewControllery que funciona bien. Este enfoque le brinda un alto nivel de personalización y control. Simplemente llame en FrameViewController.display(_)cualquier momento que desee reemplazar el controlador raíz en su ventana, y hará ese trabajo por usted.

Aleks N.
fuente
2

Esta es una actualización para swift 3, este método debe estar en el delegado de su aplicación, y puede llamarlo desde cualquier controlador de vista, a través de una instancia compartida del delegado de la aplicación

func logOutAnimation() {
    let storyBoard = UIStoryboard.init(name: "SignIn", bundle: nil)
    let viewController = storyBoard.instantiateViewController(withIdentifier: "signInVC")
    UIView.transition(with: self.window!, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromLeft, animations: {
        self.window?.rootViewController = viewController
        self.window?.makeKeyAndVisible()
    }, completion: nil)
}

La parte que falta en varias preguntas anteriores, es

    self.window?.makeKeyAndVisible()

Espero que esto ayude a alguien.

Giovanny Piñeros
fuente
1

en AppDelegate.h:

#define ApplicationDelegate ((AppDelegate *)[UIApplication sharedApplication].delegate)]

en su controlador:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
    ApplicationDelegate.window.rootViewController = newViewController;
    }
                completion:nil];
Beto
fuente
66
Esto es lo mismo que la respuesta aceptada, excepto que el formato es incorrecto. ¿Por qué molestarse?
jrturton
1
Este no depende de que estés en una Vista o ViewController. La mayor diferencia es más filosófica en términos de cuán grueso o delgado le gusta que sean sus Vistas y Controladores de Vistas.
Max
0

Propongo a mi manera, que está funcionando bien en mi proyecto, y me ofrece buenas animaciones. He probado otras propuestas encontradas en esta publicación, pero algunas de ellas no funcionan como se esperaba.

- (void)transitionToViewController:(UIViewController *)viewController withTransition:(UIViewAnimationOptions)transition completion:(void (^)(BOOL finished))completion {
// Reset new RootViewController to be sure that it have not presented any controllers
[viewController dismissViewControllerAnimated:NO completion:nil];

[UIView transitionWithView:self.window
                  duration:0.5f
                   options:transition
                animations:^{
                    for (UIView *view in self.window.subviews) {
                        [view removeFromSuperview];
                    }
                    [self.window addSubview:viewController.view];

                    self.window.rootViewController = viewController;
                } completion:completion];
}
93sauu
fuente
0

Bonita y dulce animación (probada con Swift 4.x):

extension AppDelegate {
   public func present(viewController: UIViewController) {
        guard let window = window else { return }
        UIView.transition(with: window, duration: 0.5, options: .transitionFlipFromLeft, animations: {
            window.rootViewController = viewController
        }, completion: nil)
    }
}

Llamar con

guard let delegate = UIApplication.shared.delegate as? AppDelegate else { return }
delegate.present(viewController: UIViewController())
Cesare
fuente