Animação de transição do comutador RootViewController

125

Existe alguma maneira de ter um efeito de transição / animação ao substituir um controlador de exibição existente como rootviewcontroller por um novo no appDelegate?

Jefferson
fonte

Respostas:

272

Você pode agrupar a alternância do rootViewControllerem um bloco de animação de transição:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newViewController; }
                completion:nil];
Ole Begemann
fonte
5
Ei, Ole, eu tentei essa abordagem, funcionou parcialmente, o problema é que meu aplicativo deve permanecer apenas no modo paisagem, mas, ao fazer a transição do rootviewcontroller, o recém-apresentado controlador de exibição é carregado em retrato no início e alterna rapidamente para o modo paisagem , como resolver isso?
Chris Chen
4
I respondeu à pergunta de Chris Chen (espero talvez!?) Na sua pergunta separada aqui: stackoverflow.com/questions/8053832/...
Kalle
1
Ei, eu quero uma transição push na mesma animação, posso conseguir isso?
Utilizador 1531343
14
Eu notei alguns problemas com isso, ou seja, elementos extraviados / elementos carregados lentamente. Por exemplo, se você não possui uma barra de navegação na raiz vc existente, animar para uma nova vc que possua uma, a animação é concluída E ENTÃO a barra de navegação é adicionada. Parece meio pateta - alguma idéia de por que isso pode ser e o que pode ser feito?
Anon_dev1234 #
1
Descobri que a chamada newViewController.view.layoutIfNeeded()antes do bloco de animação corrige problemas com elementos carregados lentamente.
Whoa
66

Eu encontrei isso e funciona perfeitamente:

no seu appDelegate:

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

no seu aplicativo

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

créditos:

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

Jesus
fonte
Essa tela de suporte automático gira?
Wingzero #
1
Esta solução funcionou melhor no meu caso. Ao usar o transiçãoWithView, o novo controlador de exibição raiz foi organizado adequadamente até depois que a transição foi concluída. Essa abordagem permite que o novo controlador de visualização raiz seja adicionado à janela, organizado e depois transicionado.
Fostah
@Wingzero um pouco atrasado, mas isso permitiria qualquer tipo de transição, seja via UIView.animations (por exemplo, um CGAffineTransform com rotate) ou um CAAnimation personalizado.
Can
41

Estou postando a resposta de Jesus implementada rapidamente. Ele pega o identificador do viewcontroller como argumento, carrega do storyboard selectedViewController e altera o rootViewController com animação.

Atualização do 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();
    });
  }

Atualização 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;
  }

Depois, você tem um uso muito simples de qualquer lugar:

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

Atualização do Swift 3.0

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.changeRootViewController(with: "listenViewController")
Neil Galiaskarov
fonte
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
fonte
O XCode corrigiu meu código assim: `` `UIView.transition (with: self.view.window !, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromTop, animações: {appDelegate.window? .RootViewController = myViewController}, conclusão: nil) ``
scaryguy 5/08/2017
10

apenas tente isso. Funciona bem para mim.

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:

Este é melhor.

- (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
fonte
Eu tive uma animação padrão estranha quando simplesmente alternei o VC raiz. A primeira versão se livrou disso para mim.
211916 juhan_h
A segunda versão animará o layout da subview, conforme mencionado por juhan_h. Se isso não for necessário, experimente remover UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviews, ou use a primeira versão ou outro método.
ftvs
3

Para não ter problemas com a transição de transição mais tarde, no aplicativo, é bom limpar a exibição antiga da pilha também

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
fonte
2

A resposta correta é que você não precisa substituí-lo rootViewControllerna sua janela. Em vez disso, crie um personalizado UIViewController, atribua-o uma vez e deixe-o exibir um controlador filho por vez e substitua-o por animação, se necessário. Você pode usar o seguinte trecho de código como ponto 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
    }
}

E a maneira como você o usa é:

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

O exemplo acima demonstra que você pode aninhar UINavigationControllerdentro FrameViewControllere que funciona muito bem. Essa abordagem oferece alto nível de personalização e controle. Basta ligar a FrameViewController.display(_)qualquer momento para substituir o controlador raiz na sua janela, e ele fará esse trabalho por você.

Aleks N.
fonte
2

Esta é uma atualização para o swift 3, esse método deve estar no delegado do aplicativo e você pode chamá-lo de qualquer controlador de exibição, por meio de uma instância compartilhada do delegado do aplicativo

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)
}

A parte que está faltando nas várias perguntas acima é

    self.window?.makeKeyAndVisible()

Espero que isso ajude alguém.

Giovanny Piñeros
fonte
1

em AppDelegate.h:

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

no seu controlador:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
    ApplicationDelegate.window.rootViewController = newViewController;
    }
                completion:nil];
Prumo
fonte
6
É o mesmo que a resposta aceita, exceto que a formatação está incorreta. Porque se importar?
Jrturton 17/01/2013
1
Este não depende de você estar em um View ou ViewController. A maior diferença é mais filosófica em termos de quão grossa ou fina você gosta de seus Views e ViewControllers.
Max
0

Proponho o meu caminho, que está funcionando bem no meu projeto, e me oferece boas animações. Testei outras propostas encontradas neste post, mas algumas delas não funcionam conforme o esperado.

- (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
fonte
0

Boa animação doce (testada com 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)
    }
}

Ligue com

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