RootViewController Switch Transition Animation


125

Esiste un modo per avere un effetto Transizione / animazione mentre si sostituisce un viewcontroller esistente come rootviewcontroller con uno nuovo in AppDelegate?

Risposte:


272

Puoi avvolgere la commutazione di rootViewControllerin un blocco animazione di transizione:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newViewController; }
                completion:nil];

5
ehi Ole, ho provato questo approccio, ha funzionato parzialmente, il fatto è che la mia app deve rimanere solo in modalità orizzontale, ma eseguendo la transizione rootviewcontroller, il controller di visualizzazione appena presentato viene caricato in verticale all'inizio e ruota rapidamente in modalità orizzontale , come risolverlo?
Chris Chen,

4
Ho risposto alla domanda di Chris Chen (si spera! Forse?) Nella sua domanda separata qui: stackoverflow.com/questions/8053832/…
Kalle

1
ehi, voglio una transizione push nella stessa animazione, posso farlo?
Utente 1531343

14
Ho notato alcuni problemi con questo, vale a dire elementi fuori posto / elementi caricati pigramente. Ad esempio, se non si dispone di una barra di navigazione sul vc radice esistente, quindi animare in un nuovo vc che ne ha uno, l'animazione viene completata E POI viene aggiunta la barra di navigazione. Sembra un po 'sciocco: qualche idea sul perché questo potrebbe essere e cosa si può fare?
anon_dev1234

1
Ho scoperto che chiamare newViewController.view.layoutIfNeeded()prima del blocco animazione risolve i problemi con elementi caricati pigramente.
Whoa,

66

Ho trovato questo e funziona perfettamente:

nella tua app

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

nella tua app

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

crediti:

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


Questo supporta la rotazione automatica dello schermo?
Wingzero,

1
Questa soluzione ha funzionato meglio nel mio caso. Quando si utilizza changeWithView, il nuovo controller della vista radice è stato correttamente disposto fino al completamento della transizione. Questo approccio consente di aggiungere il nuovo controller della vista radice alla finestra, disposto e quindi trasferito.
Fostah,

@Wingzero un po 'in ritardo, ma ciò consentirebbe qualsiasi tipo di transizione, tramite UIView.animations (diciamo, un CGAffineTransform con rotazione) o una CAAnimation personalizzata.
Can

41

Sto postando la risposta di Gesù implementata in breve tempo. Prende l'identificatore di viewcontroller come argomento, carica dallo storyViewViewController desiderato e modifica rootViewController con l'animazione.

Aggiornamento 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();
    });
  }

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

Dopo, hai un utilizzo molto semplice da qualsiasi luogo:

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

Aggiornamento di Swift 3.0

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

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)

XCode ha risolto il mio codice in questo modo: `` `UIView.transition (con: self.view.window!, Durata: 0,5, opzioni: UIViewAnimationOptions.transitionFlipFromTop, animazioni: {appDelegate.window? .RootViewController = myViewController}, completamento: zero) ``
scaryguy il

10

prova questo. Funziona bene per me.

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

MODIFICARE:

Questo è migliore.

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

Ho avuto una strana animazione predefinita semplicemente cambiando VC root. La prima versione me ne è sbarazzata.
juhan_h,

La seconda versione animerà il layout della subview, come menzionato da juhan_h. Se ciò non è necessario, provare a rimuovere UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviewso utilizzare la prima versione o un altro metodo.
ftvs

3

Per non avere problemi con la transizione, capovolgi in seguito nell'app è bene cancellare anche la vecchia vista dallo stack

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

2

La risposta corretta è che non è necessario sostituire la rootViewControllerfinestra. Invece, crea un'abitudine UIViewController, assegnala una volta e lascia che visualizzi un controller figlio alla volta e, se necessario, sostituiscilo con l'animazione. È possibile utilizzare il seguente pezzo di codice come punto di partenza:

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 il modo in cui lo usi è:

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

L'esempio sopra mostra che puoi annidare UINavigationControllerall'interno FrameViewControllere che funziona bene. Questo approccio offre un alto livello di personalizzazione e controllo. Chiama FrameViewController.display(_)quando vuoi sostituire il controller di root nella tua finestra e farà quel lavoro per te.


2

Questo è un aggiornamento per Swift 3, questo metodo dovrebbe essere nel tuo delegato dell'app e lo chiami da qualsiasi controller di visualizzazione, tramite un'istanza condivisa del delegato dell'app

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 che manca a varie domande sopra, è

    self.window?.makeKeyAndVisible()

Spero che questo aiuti qualcuno.


1

in AppDelegate.h:

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

nel tuo controller:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
    ApplicationDelegate.window.rootViewController = newViewController;
    }
                completion:nil];

6
È uguale alla risposta accettata, tranne per il fatto che la formattazione è errata. Perché preoccuparsi?
jrturton,

1
Questo non dipende dal fatto che tu sia in View o ViewController. La differenza più grande è più filosofica in termini di quanto ti piacciono i tuoi Views e ViewController.
Max

0

Propongo la mia strada che sta funzionando bene nel mio progetto e mi offre buone animazioni. Ho testato altre proposte trovate in questo post, ma alcune non funzionano come previsto.

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

0

Simpatica animazione (testata 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)
    }
}

Chiama con

guard let delegate = UIApplication.shared.delegate as? AppDelegate else { return }
delegate.present(viewController: UIViewController())
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.