Animation de transition de commutateur RootViewController

125

Existe-t-il un moyen d'avoir un effet de transition / animation tout en remplaçant un viewcontroller existant en tant que rootviewcontroller par un nouveau dans l'appDelegate?

Jefferson
la source

Réponses:

272

Vous pouvez encapsuler la commutation de rootViewControllerdans un bloc d'animation de transition:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newViewController; }
                completion:nil];
Ole Begemann
la source
5
hé Ole, j'ai essayé cette approche, cela a fonctionné partiellement, le fait est que mon application ne restera qu'en mode paysage, mais en faisant la transition rootviewcontroller, le contrôleur de vue nouvellement présenté est chargé en portrait au début, et tourne rapidement en mode paysage , comment résoudre ça?
Chris Chen
4
J'ai répondu à la question de Chris Chen (espérons-le! Peut-être?) Dans sa question distincte ici: stackoverflow.com/questions/8053832/…
Kalle
1
hé je veux une transition push dans la même animation puis-je y parvenir?
Utilisateur 1531343
14
J'ai remarqué quelques problèmes avec cela, à savoir des éléments mal placés / des éléments chargés paresseusement. Par exemple, si vous n'avez pas de barre de navigation sur le vc racine existant, puis animez vers un nouveau vc qui en a un, l'animation se termine ET PUIS la barre de navigation est ajoutée. Cela semble un peu maladroit - des pensées sur pourquoi cela peut être, et ce qui peut être fait?
anon_dev1234
1
J'ai trouvé que l'appel newViewController.view.layoutIfNeeded()avant le bloc d'animation corrige les problèmes avec les éléments chargés paresseusement.
Whoa
66

J'ai trouvé cela et fonctionne parfaitement:

dans votre applicationDelegate:

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

dans votre application

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

crédits:

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

Jésus
la source
Est-ce que cela prend en charge la rotation automatique de l'écran?
Wingzero
1
Cette solution fonctionnait mieux dans mon cas. Lors de l'utilisation de transitionWithView, le nouveau contrôleur de vue racine était correctement disposé jusqu'à la fin de la transition. Cette approche permet au nouveau contrôleur de vue racine d'être ajouté à la fenêtre, mis en page puis transféré.
Fostah
@Wingzero un peu en retard, mais cela permettrait toute sorte de transition, soit via UIView.animations (par exemple, un CGAffineTransform avec rotate) ou une CAAnimation personnalisée.
Can
41

Je publie la réponse de Jésus mise en œuvre rapidement. Il prend l'identifiant de viewcontroller comme argument, se charge à partir du storyboard désiréViewController et change rootViewController avec une animation.

Mise à jour 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();
    });
  }

Mise à jour 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;
  }

Après, vous avez une utilisation très simple de n'importe où:

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

Mise à jour Swift 3.0

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.changeRootViewController(with: "listenViewController")
Neil Galiaskarov
la source
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
la source
XCode a corrigé mon code comme ceci: `` UIView.transition (avec: self.view.window !, durée: 0.5, options: UIViewAnimationOptions.transitionFlipFromTop, animations: {appDelegate.window? .RootViewController = myViewController}, complétion: nil) ``
scaryguy
10

essayez juste ceci. Fonctionne bien pour moi.

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

ÉDITER:

Celui ci est mieux.

- (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
la source
J'ai eu une animation par défaut étrange en changeant simplement de VC racine. La première version m'a débarrassé de cela.
juhan_h
La deuxième version animera la disposition de la sous-vue, comme mentionné par juhan_h. Si ce n'est pas nécessaire, essayez de supprimer UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviews, ou utilisez la première version ou une autre méthode.
ftvs
3

Afin de ne pas avoir de problèmes avec le basculement de transition plus tard dans l'application, il est bon d'effacer également l'ancienne vue de la pile.

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
la source
2

La bonne réponse est que vous n'avez pas besoin de remplacer le rootViewControllersur votre fenêtre. Au lieu de cela, créez une personnalisation UIViewController, attribuez-la une fois et laissez-la afficher un contrôleur enfant à la fois et remplacez-la par une animation si nécessaire. Vous pouvez utiliser le morceau de code suivant comme point de départ:

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

Et la façon dont vous l'utilisez est:

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

L'exemple ci-dessus montre que vous pouvez imbriquer à l' UINavigationControllerintérieur FrameViewControlleret que cela fonctionne très bien. Cette approche vous offre un niveau élevé de personnalisation et de contrôle. Appelez simplement à FrameViewController.display(_)chaque fois que vous souhaitez remplacer le contrôleur racine de votre fenêtre, et il fera ce travail pour vous.

Aleks N.
la source
2

Ceci est une mise à jour pour swift 3, cette méthode doit être dans votre délégué d'application, et vous l'appelez depuis n'importe quel contrôleur de vue, via une instance partagée du délégué d'application

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 partie qui manque aux diverses questions ci-dessus est

    self.window?.makeKeyAndVisible()

J'espère que cela aide quelqu'un.

Giovanny Piñeros
la source
1

dans AppDelegate.h:

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

dans votre contrôleur:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
    ApplicationDelegate.window.rootViewController = newViewController;
    }
                completion:nil];
Bob
la source
6
C'est la même chose que la réponse acceptée, sauf que le formatage est incorrect. Pourquoi s'embêter?
jrturton
1
Celui-ci ne dépend pas de votre présence dans un View ou ViewController. La plus grande différence est plus philosophique en termes d'épaisseur ou de finesse que vous aimez de vos vues et de vos ViewControllers.
Max
0

Je propose à ma façon que cela fonctionne bien dans mon projet, et il me propose de bonnes animations. J'ai testé d'autres propositions trouvées dans cet article, mais certaines d'entre elles ne fonctionnent pas comme prévu.

- (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
la source
0

Belle animation douce (testée avec 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)
    }
}

Appeler avec

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