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?

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

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


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

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)

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

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

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.


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.


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

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

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())
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.