RootViewController - animacja przejścia przełącznika

125

Czy istnieje sposób, aby uzyskać efekt przejścia / animacji podczas zastępowania istniejącego kontrolera widoku jako rootviewcontroller nowym w appDelegate?

Jefferson
źródło

Odpowiedzi:

272

Możesz zawinąć przełączanie rootViewControllerw bloku animacji przejścia:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newViewController; }
                completion:nil];
Ole Begemann
źródło
5
hej Ole, wypróbowałem to podejście, częściowo zadziałało, chodzi o to, że moja aplikacja pozostanie tylko w trybie poziomym, ale wykonując przejście rootviewcontroller, nowo prezentowany kontroler widoku jest ładowany w orientacji pionowej na początku i szybko obraca się do trybu poziomego , jak to rozwiązać?
Chris Chen
4
Odpowiedziałem na pytanie Chrisa Chena (mam nadzieję, że może?) W jego oddzielnym pytaniu tutaj: stackoverflow.com/questions/8053832/…
Kalle,
1
hej, chcę przejść push w tej samej animacji, czy mogę to osiągnąć?
Użytkownik 1531343
14
Zauważyłem pewne problemy z tym, a mianowicie niewłaściwie umieszczone elementy / leniwie ładowane elementy. Na przykład, jeśli nie masz paska nawigacyjnego na istniejącym głównym vc, a następnie wykonaj animację do nowego vc, który ma taki, animacja zostanie zakończona, ORAZ pasek nawigacji zostanie dodany. Wygląda to trochę głupio - jakieś przemyślenia, dlaczego tak się dzieje i co można zrobić?
anon_dev1234
1
Odkryłem, że wywołanie newViewController.view.layoutIfNeeded()przed blokiem animacji rozwiązuje problemy z leniwie ładowanymi elementami.
Whoa,
66

Znalazłem to i działa idealnie:

w swojej aplikacji

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

w Twojej aplikacji

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

kredyty:

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

Jezus
źródło
Czy to obsługuje automatyczne obracanie ekranu?
Wingzero
1
To rozwiązanie działało lepiej w moim przypadku. W przypadku korzystania z transitWithView nowy główny kontroler widoku był prawidłowo ułożony do czasu zakończenia przejścia. Takie podejście umożliwia dodanie nowego kontrolera widoku głównego do okna, rozmieszczenie, a następnie przeniesienie.
Fostah
@Wingzero trochę późno, ale pozwoliłoby to na dowolne przejście, albo przez UIView.animations (powiedzmy, CGAffineTransform with rotate) lub niestandardową CAAnimation.
Może
41

Wysyłam odpowiedź Jezusa zaimplementowaną szybko. Pobiera identyfikator viewcontrollera jako argument, ładuje z storyboardu pożądanegoViewController i zmienia rootViewController z animacją.

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

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

Potem masz bardzo proste użycie z dowolnego miejsca:

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

Aktualizacja Swift 3.0

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.changeRootViewController(with: "listenViewController")
Neil Galiaskarov
źródło
25

Szybki 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
źródło
XCode naprawił mój kod w ten sposób: `` UIView.transition (with: self.view.window !, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromTop, animations: {appDelegate.window? .RootViewController = myViewController}, zakończenie: nil) ``
scaryguy
10

po prostu spróbuj tego. U mnie działa dobrze.

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

EDYTOWAĆ:

Ten jest lepszy.

- (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
źródło
Miałem dziwną domyślną animację, gdy po prostu przełączałem główny VC. Pierwsza wersja pozbyła się tego dla mnie.
juhan_h
Druga wersja będzie animować układ subview, o czym wspomina juhan_h. Jeśli nie jest to wymagane, poeksperymentuj z usunięciem UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviewslub użyj pierwszej wersji lub innej metody.
ftvs
3

Aby później w aplikacji nie mieć problemów z przejściem, dobrze jest usunąć również stary widok ze stosu

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
źródło
2

Prawidłowa odpowiedź brzmi: nie musisz wymieniać rootViewControllerokna. Zamiast tego utwórz niestandardowy UIViewController, przypisz go raz i pozwól mu wyświetlać jeden kontroler podrzędny na raz i zastąp go animacją, jeśli to konieczne. Możesz użyć następującego fragmentu kodu jako punktu wyjścia:

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

A sposób, w jaki go używasz, to:

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

Powyższy przykład pokazuje, że możesz zagnieżdżać się w UINavigationControllerśrodku FrameViewControlleri to działa dobrze. Takie podejście zapewnia wysoki poziom dostosowywania i kontroli. Po prostu zadzwoń w FrameViewController.display(_)dowolnym momencie, gdy chcesz wymienić kontroler root w swoim oknie, a on wykona tę pracę za Ciebie.

Aleks N.
źródło
2

To jest aktualizacja dla Swift 3, ta metoda powinna znajdować się w delegacie aplikacji i wywołać ją z dowolnego kontrolera widoku za pośrednictwem udostępnionego wystąpienia delegata aplikacji

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

Częścią, której brakuje w różnych pytaniach powyżej, jest

    self.window?.makeKeyAndVisible()

Mam nadzieję, że to komuś pomoże.

Giovanny Piñeros
źródło
1

w AppDelegate.h:

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

w kontrolerze:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
    ApplicationDelegate.window.rootViewController = newViewController;
    }
                completion:nil];
Pion
źródło
6
To jest to samo, co zaakceptowana odpowiedź, z wyjątkiem nieprawidłowego formatowania. Po co się męczyć?
jrturton
1
Ten nie zależy od tego, czy jesteś w widoku lub kontrolerze ViewController. Największa różnica jest bardziej filozoficzna pod względem tego, jak grube lub cienkie są Twoje widoki i kontrolery widoku.
Max
0

Proponuję sposób, w jaki działa dobrze w moim projekcie i oferuje mi dobre animacje. Przetestowałem inne propozycje znalezione w tym poście, ale niektóre z nich nie działają zgodnie z oczekiwaniami.

- (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
źródło
0

Ładna, słodka animacja (testowana ze 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)
    }
}

Zadzwoń z

guard let delegate = UIApplication.shared.delegate as? AppDelegate else { return }
delegate.present(viewController: UIViewController())
Cesare
źródło