使用动画交换rootViewController

时间:2012-01-11 22:50:14

标签: objective-c ios cocoa-touch uiviewcontroller

我在使用动画交换rootViewControllers方面遇到了一些麻烦。这是我正在使用的代码:

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

除了在动画之前,屏幕变为黑色然后动画发生之外,它有点有效。看起来原始的rootViewController在动画之前就被删除了。有什么想法吗?

7 个答案:

答案 0 :(得分:49)

transitionWithView旨在为指定容器视图的子视图设置动画。更改根视图控制器的动画并不是那么简单。我花了很长时间试图做它没有副作用。参见:

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

编辑:添加摘录参考答案

[UIView transitionFromView:self.window.rootViewController.view
                    toView:viewController.view
                  duration:0.65f
                   options:transition
                completion:^(BOOL finished){
                    self.window.rootViewController = viewController;
                }];

答案 1 :(得分:43)

我知道这是一个相当古老的问题,然而,接受的答案和替代方案都没有提供一个好的解决方案(当动画完成新的根视图控制器的导航栏从白色闪烁到背景颜色,这是非常刺耳)。

我通过快照第一个视图控制器的最后一个屏幕,将其覆盖在第二个(目标)视图控制器上,然后按照我想要的动画我将第二个视图控制器设置为根:

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

编辑: Swift 3版本的代码:

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

答案 2 :(得分:5)

我发现transitionWithView:duration:options:animations:completion:可以产生更可靠的结果。

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

如果您将其保留到完成块以设置根视图控制器,那么在视图(将/将会出现)等方法中出现

self.view.window.rootViewController

仍将设置为上一个视图控制器。在大多数情况下这可能不是问题,除非您需要在这些方法中将对rootViewController的引用传递给其他代码。

答案 3 :(得分:2)

对我有用的解决方案是对Marko Nikolovski的回答略有修改。我现有的根视图控制器上有一个动画微调器,因此快照看起来很奇怪,因为它冻结了动画。最后,我能够执行以下操作(在当前根视图控制器内):

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

答案 4 :(得分:2)

当前接受的答案(在撰写此答案时)并不理想,因为它可能无法在动画期间正确定位导航栏等UI元素或导致其他布局故障Marko Nikolovski's answer中所述。但是,将当前视图控制器视图的快照推送到下一个视图控制器视图的顶部以用于动画目的并不理想,因为它不能用于视图控制器,其中在转换到下一个视图控制器期间发生任何类型的动画作为快照只是静态图像

cclogg's basic idea放在我的实现中,以便在两个视图控制器之间平滑过渡,而不会对alpha值感到烦恼:

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

在启动转换或动画之前替换窗口的rootViewController 非常重要,因为这是新视图控制器如何知道它的上下文,即正确的布局边距等。

如果出于某种原因确实需要替换窗口rootViewController ,这是一种方法。但是,我想指出,从架构的角度来看,我认为更好的方法是创建一个视图控制器,它独自负责处理两个其他视图控制器之间的转换,并将其设置为窗口& #39; s rootViewController然后永不改变。它在this answer中对类似的问题进行了很好的解释。

答案 5 :(得分:0)

从ViewController

let vc = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
    
let transition = CATransition()
transition.type = .push
transition.subtype = .fromRight
    
let window = self.view.window
window?.layer.add(transition, forKey: kCATransition)
window?.rootViewController = vc
window?.makeKeyAndVisible()

答案 6 :(得分:-2)

为什么不创建自定义视图,或者只是创建UINavigationController(不要显示标题栏)并放置" rootViews"而不是交换rootViewControllers。你想在那之间交换。