Анимация перехода переключателя RootViewController

126

Есть ли способ получить эффект перехода / анимации при замене существующего контроллера просмотра как rootviewcontroller новым в appDelegate?

Джефферсон
источник

Ответы:

273

Вы можете заключить переключение rootViewControllerв блок анимации перехода:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newViewController; }
                completion:nil];
Оле Бегеманн
источник
5
Привет, Оле, я попробовал этот подход, он работал частично, дело в том, что мое приложение должно оставаться только в ландшафтном режиме, но при выполнении перехода rootviewcontroller вновь представленный контроллер представления загружается в портретном режиме в начале и быстро переключается в альбомный режим , как это решить?
Крис Чен
4
Я ответил на вопрос Криса Чена (надеюсь! Может быть?) В его отдельном вопросе здесь: stackoverflow.com/questions/8053832/…
Kalle
1
эй, я хочу push-переход в той же анимации, могу ли я этого добиться?
Пользователь 1531343,
14
Я заметил некоторые проблемы с этим, а именно неуместные элементы / лениво загруженные элементы. Например, если у вас нет панели навигации на существующем корневом виртуальном компьютере, а затем выполните анимацию для нового виртуального компьютера, у которого он есть, анимация завершится, И ЗАТЕМ добавится панель навигации. Выглядит немного глупо - есть мысли о том, почему это может быть и что можно сделать?
anon_dev1234 02
1
Я обнаружил, что вызов newViewController.view.layoutIfNeeded()перед блоком анимации устраняет проблемы с лениво загруженными элементами.
Whoa
66

Я нашел это и отлично работает:

в вашем 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];
    }];
}

в вашем приложении

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

кредиты:

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

Иисус
источник
Поддерживает ли это автоматический поворот экрана?
Wingzero
1
В моем случае это решение сработало лучше. При использовании transitionWithView новый контроллер корневого представления был правильно размещен до завершения перехода. Такой подход позволяет добавить новый контроллер корневого представления в окно, расположить его и затем перенести.
Fostah 04
@Wingzero немного поздно, но это позволит любой переход, либо через UIView.animations (скажем, CGAffineTransform с поворотом), либо через настраиваемую CAAnimation.
Может
41

Я отправляю ответ Иисуса, реализованный быстро. Он принимает идентификатор viewcontroller в качестве аргумента, загружает из раскадровки желаемый ViewController и изменяет rootViewController с помощью анимации.

Обновление 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();
    });
  }

Обновление 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;
  }

После этого у вас будет очень простое использование из любого места:

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

Swift 3.0 обновить

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.changeRootViewController(with: "listenViewController")
Нил Галиаскаров
источник
26

Swift 2

UIView.transitionWithView(self.window!, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromLeft, animations: {
  self.window?.rootViewController = anyViewController
}, completion: nil)

Свифт 3, 4, 5

UIView.transition(with: self.window!, duration: 0.5, options: UIView.AnimationOptions.transitionFlipFromLeft, animations: {
  self.window?.rootViewController = anyViewController
}, completion: nil)
Чандрашекхар HM
источник
XCode исправил мой код следующим образом: `` UIView.transition (with: self.view.window !, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromTop, анимация: {appDelegate.window? .RootViewController = myViewController}, завершение: nil) ``
scaryguy
10

просто попробуйте это. У меня отлично работает.

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

РЕДАКТИРОВАТЬ:

Этот лучше.

- (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);
        }
    }];
}
Дмитрий Колеров
источник
У меня была странная анимация по умолчанию при простом переключении корневого VC. Первая версия избавилась от этого для меня.
juhan_h 03
Вторая версия будет анимировать макет subview, как упоминалось juhan_h. Если этого не требуется, либо поэкспериментируйте с удалением UIViewAnimationOptionAllowAnimatedContent|UIViewAnimationOptionLayoutSubviews, либо используйте первую версию, либо какой-нибудь другой метод.
ftvs
3

Чтобы не было проблем с переворотом перехода позже в приложении, хорошо также очистить старый вид из стека.

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

Правильный ответ: вам не нужно заменять значок rootViewControllerна вашем окне. Вместо этого создайте заказ UIViewController, назначьте его один раз и позвольте ему отображать по одному дочернему контроллеру за раз и при необходимости заменить его анимацией. Вы можете использовать следующий фрагмент кода в качестве отправной точки:

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

И способ его использования:

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

Приведенный выше пример демонстрирует, что вы можете вкладываться UINavigationControllerвнутрь, FrameViewControllerи это прекрасно работает. Такой подход дает вам высокий уровень настройки и контроля. Просто звоните в FrameViewController.display(_)любое время, когда вы захотите заменить корневой контроллер в своем окне, и он выполнит эту работу за вас.

Алекс Н.
источник
2

Это обновление для Swift 3, этот метод должен быть в делегате вашего приложения, и вы вызываете его из любого контроллера представления через общий экземпляр делегата приложения.

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

Часть, которая отсутствует в различных вопросах выше, это

    self.window?.makeKeyAndVisible()

Надеюсь, это кому-то поможет.

Джованни Пиньерос
источник
1

в AppDelegate.h:

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

в вашем контроллере:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
    ApplicationDelegate.window.rootViewController = newViewController;
    }
                completion:nil];
боб
источник
6
Это то же самое, что принятый ответ, за исключением неправильного форматирования. Зачем беспокоиться?
jrturton
1
Это не зависит от того, находитесь ли вы в View или ViewController. Самая большая разница - более философская с точки зрения того, насколько толстыми или тонкими вам нравятся ваши представления и ViewController.
Макс
0

Я предлагаю свой способ, который отлично работает в моем проекте, и предлагает мне хорошие анимации. Я протестировал другие предложения, найденные в этом посте, но некоторые из них не работают должным образом.

- (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
источник
0

Приятная милая анимация (проверено на 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)
    }
}

Звоните с

guard let delegate = UIApplication.shared.delegate as? AppDelegate else { return }
delegate.present(viewController: UIViewController())
Чезаре
источник