Swapping rootViewController with animation

2019-01-16 12:43发布

I'm having a little trouble swapping rootViewControllers with animation. Here's the code that I'm using:

[UIView transitionWithView:self.window duration:0.8 options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
        self.window.rootViewController = self.navigationController;
    } completion:nil];

It kind of works except that right before the animation, the screen turns to black and then the animation occurs. It looks like the original rootViewController is getting removed right before the animation. Any ideas?

6条回答
够拽才男人
2楼-- · 2019-01-16 13:05

I am aware this is a quite old question, however, neither the accepted answer nor the alternatives provided a good solution (when the animation finished the navigation bar of the new root view controller blinked from white to the background color, which is very jarring).

I fixed this by snapshotting the last screen of the first view controller, overlaying it over the second (destination) view controller, and then animating it as I wanted after I set the second view controller as root:

UIView *overlayView = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
[self.destinationViewController.view addSubview:overlayView];
self.window.rootViewController = self.destinationViewController;

[UIView animateWithDuration:0.4f delay:0.0f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
    overlayView.alpha = 0;
} completion:^(BOOL finished) {
    [overlayView removeFromSuperview];
}];

EDIT: Swift 3 version of the code:

let overlayView = UIScreen.main.snapshotView(afterScreenUpdates: false)
destinationViewController.view.addSubview(overlayView)
window.rootViewController = destinationViewController

UIView.animate(withDuration: 0.4, delay: 0, options: .transitionCrossDissolve, animations: {
    overlayView.alpha = 0
}, completion: { finished in
    overlayView.removeFromSuperview()
})
查看更多
祖国的老花朵
3楼-- · 2019-01-16 13:13

transitionWithView is intended to animate subviews of the specified container view. It is not so simple to animate changing the root view controller. I've spent a long time trying to do it w/o side effects. See:

Animate change of view controllers without using navigation controller stack, subviews or modal controllers?

EDIT: added excerpt from referenced answer

[UIView transitionFromView:self.window.rootViewController.view
                    toView:viewController.view
                  duration:0.65f
                   options:transition
                completion:^(BOOL finished){
                    self.window.rootViewController = viewController;
                }];
查看更多
冷血范
4楼-- · 2019-01-16 13:21

I have found transitionWithView:duration:options:animations:completion: to produce a more reliable result.

[UIView transitionWithView:window
                  duration:0.3
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
                    [fromView removeFromSuperview];
                    [window addSubview:toView];
                    window.rootViewController = toViewController;
                }
                completion:NULL];

If you leave it until the completion block to set the root view controller then during methods such as view(Will/Did)Appear

self.view.window.rootViewController

Will still be set to the previous view controller. This may not be a problem in most situations unless you need to pass on that reference to the rootViewController to other code during those methods as I did.

查看更多
可以哭但决不认输i
5楼-- · 2019-01-16 13:22

The currently accepted answer (at the time of writing this answer) isn't ideal because it might not position UI elements like the navigation bar correctly during the animation or lead to other layout glitches as described in Marko Nikolovski's answer. However, pushing a snapshot of the current view controller's view on top of the next view controller's view for animation purposes isn't ideal either because it cannot be used for view controllers in which any kind of animation is happening during the transition to the next view controller as a snapshot is just a static image.

Taking cclogg's basic idea here's my implementation for a smooth transition between two view controllers that doesn't bother about alpha values:

/// Replaces the window's current `rootViewController` with the `newViewController` 
/// passed as a parameter. If `animated`, the window animates the replacement 
/// with a cross dissolve transition.
///
/// - Parameters:
///   - newViewController: The view controller that replaces the current view controller.
///   - animated: Specifies if the transition between the two view controllers is animated.
///   - duration: The transition's duration. (Ignored if `animated == false`)
private func swapCurrentViewController(with newViewController: UIViewController, 
                                       animated: Bool = true, 
                                       duration: TimeInterval = 1) {

    // Get a reference to the window's current `rootViewController` (the "old" one)
    let oldViewController = window?.rootViewController

    // Replace the window's `rootViewController` with the new one
    window?.rootViewController = newViewController

    if animated, let oldView = oldViewController?.view {

        // Add the old view controller's view on top of the new `rootViewController`
        newViewController.view.addSubview(oldView)

        // Remove the old view controller's view in an animated fashion
        UIView.transition(with: window!,
                          duration: duration,
                          options: .transitionCrossDissolve,
                          animations: { oldView.removeFromSuperview() },
                          completion: nil)
    }
}

It's important to replace the window's rootViewController before initiating the transition or animation because that's how the new view controller gets to know its context, i.e. the correct layout margins etc.


查看更多
乱世女痞
6楼-- · 2019-01-16 13:26

The solution that worked for me was a slight modification of Marko Nikolovski's answer. My existing root view controller had an animated spinner on it, so a snapshot looked weird because it froze the animation. In the end, I was able to do the following (inside the current root view controller):

NextRootViewController *nextRootVC = [self.storyboard instantiateViewControllerWithIdentifier:@"NextRootViewController"];

self.view.window.rootViewController = nextRootVC;

[nextRootVC addSubview:self.view];

[UIView animateWithDuration:0.4 delay:0.2 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
    self.view.alpha = 0;
} completion:^(BOOL finished) {
    [self.view removeFromSuperview];
}];
查看更多
Rolldiameter
7楼-- · 2019-01-16 13:26

Instead of swapping rootViewControllers, why don't you create either a custom View, or maybe just a UINavigationController (don't show titlebar) and put the "rootViews" you want to swap between under that.

查看更多
登录 后发表回答