RootViewController Switch Transition Animation

125

Gibt es eine Möglichkeit, einen Übergangs- / Animationseffekt zu erzielen, während ein vorhandener Viewcontroller als Rootviewcontroller durch einen neuen im appDelegate ersetzt wird?

Jefferson
quelle

Antworten:

272

Sie können das Umschalten des rootViewControllerin einen Übergangsanimationsblock einschließen:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newViewController; }
                completion:nil];
Ole Begemann
quelle
5
hey Ole, ich habe diesen Ansatz ausprobiert, er hat teilweise funktioniert. Meine App soll nur im Querformat bleiben. Durch den Rootviewcontroller-Übergang wird der neu präsentierte View Controller zu Beginn im Hochformat geladen und schnell in den Querformatmodus gedreht , wie kann man das lösen?
Chris Chen
4
Ich beantwortete Chris Chens Frage (hoffentlich! Vielleicht?) In seiner separaten Frage hier: stackoverflow.com/questions/8053832/…
Kalle
1
Hey, ich möchte einen Push-Übergang in derselben Animation. Kann ich das erreichen?
Benutzer 1531343
14
Ich habe einige Probleme damit bemerkt, nämlich falsch platzierte Elemente / träge geladene Elemente. Wenn Sie beispielsweise keine Navigationsleiste in der vorhandenen Root-VC haben und dann zu einer neuen VC animieren, die eine hat, wird die Animation abgeschlossen UND DANN wird die Navigationsleiste hinzugefügt. Es sieht irgendwie doof aus - irgendwelche Gedanken darüber, warum das so ist und was getan werden kann?
anon_dev1234
1
Ich habe festgestellt, dass das Aufrufen newViewController.view.layoutIfNeeded()vor dem Animationsblock Probleme mit träge geladenen Elementen behebt.
Whoa
66

Ich habe das gefunden und funktioniert perfekt:

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

in Ihrer App

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

Credits:

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

Jesus
quelle
Unterstützt dies das automatische Drehen des Bildschirms?
Wingzero
1
Diese Lösung hat in meinem Fall besser funktioniert. Bei Verwendung von TransitionWithView wurde der neue Root-View-Controller bis zum Abschluss des Übergangs ordnungsgemäß angelegt. Mit diesem Ansatz kann der neue Root-View-Controller zum Fenster hinzugefügt, angelegt und dann umgestellt werden.
Fostah
@Wingzero etwas spät, aber dies würde jede Art von Übergang ermöglichen, entweder über UIView.animations (z. B. eine CGAffineTransform mit Drehung) oder eine benutzerdefinierte CAAnimation.
Kann
41

Ich poste die schnell umgesetzte Jesus-Antwort. Es nimmt die Kennung von viewcontroller als Argument, lädt aus dem Storyboard den gewünschten ViewController und ändert rootViewController mit Animation.

Swift 3.0 Update:

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

Swift 2.2 Update:

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

Danach haben Sie eine sehr einfache Verwendung von überall:

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

Swift 3.0 Update

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

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
quelle
XCode hat meinen Code folgendermaßen korrigiert: `` `UIView.transition (mit: self.view.window!, Dauer: 0.5, Optionen: UIViewAnimationOptions.transitionFlipFromTop, Animationen: {appDelegate.window? .RootViewController = myViewController}, Abschluss: nil) `` `
Scaryguy
10

versuchen Sie es einfach. Funktioniert gut für mich.

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

BEARBEITEN:

Dieses hier ist besser.

- (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
quelle
Ich hatte eine seltsame Standardanimation, als ich einfach die Root-VC wechselte. Die erste Version hat das für mich beseitigt.
juhan_h
Die zweite Version animiert das Layout der Unteransicht, wie von juhan_h erwähnt. Wenn dies nicht erforderlich ist, experimentieren Sie entweder mit dem Entfernen UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviewsoder verwenden Sie die erste Version oder eine andere Methode.
ftvs
3

Um später in der App keine Probleme mit dem Übergang zu haben, ist es gut, die alte Ansicht auch vom Stapel zu löschen

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

Die richtige Antwort ist, dass Sie die rootViewControllerin Ihrem Fenster nicht ersetzen müssen . Erstellen Sie stattdessen eine benutzerdefinierte Datei UIViewController, weisen Sie sie einmal zu, lassen Sie sie jeweils einen untergeordneten Controller anzeigen und ersetzen Sie sie bei Bedarf durch eine Animation. Sie können den folgenden Code als Ausgangspunkt verwenden:

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

Und wie Sie es verwenden, ist:

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

Das obige Beispiel zeigt , dass Sie nisten im UINavigationControllerInneren FrameViewControllerund das funktioniert ganz gut. Dieser Ansatz bietet Ihnen ein hohes Maß an Anpassung und Kontrolle. Rufen FrameViewController.display(_)Sie einfach an, wann immer Sie den Root-Controller in Ihrem Fenster ersetzen möchten, und er erledigt diese Aufgabe für Sie.

Aleks N.
quelle
2

Dies ist ein Update für Swift 3, diese Methode sollte sich in Ihrem App-Delegaten befinden und Sie rufen sie von einem beliebigen View-Controller über eine gemeinsam genutzte Instanz des App-Delegaten auf

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

Der Teil, der in verschiedenen Fragen oben fehlt, ist

    self.window?.makeKeyAndVisible()

Hoffe das hilft jemandem.

Giovanny Piñeros
quelle
1

in AppDelegate.h:

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

in Ihrem Controller:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
    ApplicationDelegate.window.rootViewController = newViewController;
    }
                completion:nil];
Bob
quelle
6
Dies entspricht der akzeptierten Antwort, außer dass die Formatierung falsch ist. Warum die Mühe?
Jrturton
1
Dieser hängt nicht davon ab, ob Sie sich in einer Ansicht oder einem ViewController befinden. Der größte Unterschied besteht darin, wie dick oder dünn Ihre Ansichten und ViewController sein sollen.
Max
0

Ich schlage meinen Weg vor, der in meinem Projekt gut funktioniert, und er bietet mir gute Animationen. Ich habe andere Vorschläge in diesem Beitrag getestet, aber einige davon funktionieren nicht wie erwartet.

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

Schöne süße Animation (getestet mit 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)
    }
}

Rufen Sie an mit

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