RootViewController Switch Transition Animation

Is there any way to have a Transition/animation effect while replacing an existing viewcontroller as rootviewcontroller with a new one in the appDelegate?

50796 次浏览

You can wrap the switching of the rootViewController in a transition animation block:

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

in AppDelegate.h:

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

in your Controller:

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

just try this. Works fine for me.

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

EDIT:

This one is better.

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

I found this and works perfectly:

in your 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 your app

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

credits:

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

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)

In order not to have problems with transition flip later on in the app is good to clear the old view from the stack as well

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

I am posting Jesus answer implemented in swift. It takes viewcontroller's identifier as an argument, loads from storyboard desiredViewController and changes rootViewController with 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;
}

After, you have a very simple usage from anywhere:

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

Swift 3.0 update

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

The correct answer is you don't need to replace the rootViewController on your window. Instead, create a custom UIViewController, assign it once and let it display one child controller at a time and replace it with animation if needed. You can use the following piece of code as a starting point:

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

And the way you use it is:

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

The example above demonstrates that you can nest UINavigationController inside FrameViewController and that works just fine. This approach gives you high level of customization and control. Just call FrameViewController.display(_) any time you would want to replace the root controller on your window, and it will do that job for you.

This is an update for swift 3, this method should be in your app delegate, and you call it from any view controller, via a shared instance of the app delegate

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

The part that is missing from various questions above, is

    self.window?.makeKeyAndVisible()

Hope this helps someone.

I propose my way which it is working fine in my project, and it offers me good animations. I have tested other proposals found in this post, but some of them do not work as expected.

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

Nice sweet animation (tested with 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)
}
}

Call with

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