RootViewController切换过渡动画


125

在appDelegate中将现有的viewcontroller替换为rootviewcontroller时,有什么方法可以具有转场/动画效果?

Answers:


272

您可以将的切换包装rootViewController在过渡动画块中:

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

5
嗨,Ole,我尝试了这种方法,但部分起作用了,问题是我的应用程序只能停留在横向模式下,但是通过进行rootviewcontroller转换,刚开始时将新呈现的视图控制器加载为纵向模式,然后快速旋转到横向模式,如何解决?
克里斯·陈

4
我在这里的另一个问题中回答了Chris Chen的问题(希望!也许是?):stackoverflow.com/questions/8053832/…–
Kalle

1
嘿,我想在同一动画中进行推送转换吗?
用户1531343年4

14
我注意到了一些问题,即元素放置错误/元素加载延迟。例如,如果您在现有的根vc上没有导航栏,然后设置为具有一个新vc的新动画,则动画将完成,然后添加导航栏。看起来有点愚蠢-关于为什么会这样以及如何做的任何想法?
anon_dev1234

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


这是否支持自动屏幕旋转?
永零2015年

1
在我的情况下,此解决方案效果更好。使用transitionWithView时,新的根视图控制器已正确布局,直到转换完成。这种方法允许将新的根视图控制器添加到窗口中,进行布局,然后进行过渡。
福斯塔

@Wingzero有点晚了,但是,这将允许任何形式的转换,可以通过UIView.animations(例如,带有旋转的CGAffineTransform)或自定义的CAAnimation。
可以

41

我要迅速发布耶稣的答案。它以viewcontroller的标识符作为参数,从情节提要板加载所需的ViewViewController并使用动画更改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")

25

迅捷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)

XCode固定了我的代码,如下所示:```UIView.transition(with:self.view.window !,持续时间:0.5,选项:UIViewAnimationOptions.transitionFlipFromTop,动画:{appDelegate.window?.rootViewController = myViewController},完成:无) ```
-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

如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,将其分配一次,然后一次显示一个子控制器,并在需要时将其替换为动画。您可以使用以下代码作为起点:

斯威夫特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中。关于您喜欢的View和ViewController的厚度,最大的区别是更具哲学性。
2014年

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

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())
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.