Objective c 使用动画交换rootViewController

Objective c 使用动画交换rootViewController,objective-c,ios,cocoa-touch,uiviewcontroller,Objective C,Ios,Cocoa Touch,Uiviewcontroller,我在用动画交换RootViewController时遇到了一些问题。以下是我正在使用的代码: [UIView transitionWithView:self.window duration:0.8 options:UIViewAnimationOptionTransitionFlipFromRight animations:^{ self.window.rootViewController = self.navigationController; } completion

我在用动画交换RootViewController时遇到了一些问题。以下是我正在使用的代码:

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

它的工作原理是,在动画开始之前,屏幕变成黑色,然后动画开始。看起来原始的rootViewController在动画开始之前就被删除了。任何想法?

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

编辑:添加了参考答案的摘录

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

与其交换rootViewControllers,不如创建一个自定义视图,或者只创建一个UINavigationController(不显示标题栏),然后将要在其中交换的“rootViews”放在下面。

我发现可以产生更可靠的结果

[UIView transitionWithView:window
                  duration:0.3
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
                    [fromView removeFromSuperview];
                    [window addSubview:toView];
                    window.rootViewController = toViewController;
                }
                completion:NULL];
如果将其保留到完成块以设置根视图控制器,则会出现视图(Will/Did)等方法


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

我知道这是一个非常老的问题,但是,无论是公认的答案还是备选方案都没有提供好的解决方案(动画完成后,新根视图控制器的导航栏从白色闪烁为背景色,这非常刺耳)

我通过快照第一个视图控制器的最后一个屏幕,将其覆盖在第二个(目标)视图控制器上,然后在将第二个视图控制器设置为根后,根据需要设置动画来修复此问题

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

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


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

下面是我在两个视图控制器之间实现平滑过渡的实现,不需要考虑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
非常重要,因为这是新视图控制器了解其上下文的方式,即正确的布局边距等


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

它在我的应用程序的其他部分工作正常。我尝试过使用你的代码,但我仍然有相同的问题。不确定你所说的“它在其他部分工作正常…”是什么意思。完全相同的代码是什么?另外,也不确定你尝试过我的代码的哪一部分。我在答案中添加了相关部分。我只是尝试了你发布的内容,现在视图在t之前发生了更改动画实际发生。在此之后,您必须执行导致问题的操作。在动画之后发布其余代码。通常,您需要等待动画完成,然后再进行其他视图更改。此代码是正确的。好的,谢谢。验收很好,但我希望您解决问题。如果您移动任何位置T-动画视图变成了完成块,这应该会有帮助。我会考虑这是最后一个办法。我正在寻找一个稍微更优雅的方法。@ EDC1591可能(只是猜测)最优雅的解决方案是定制的“SwapViewController”。这将为您执行动画转换。可能他稍后在应用程序的某个位置引用了window.rootViewController,这就是您可能希望保留vc rvc的原因。您保存了我的一天:)谢谢!!我尝试了许多其他的解决方案,这是我发现的最平滑的动画。我也喜欢这种简单的方法。谢谢大家。这是我找到的UIView.transitionFromView的最佳替代品谢谢你,伙计!简单而微妙!我喜欢你的主意!这个答案值得更多的投票:D像一个符咒一样工作,解决了这个小问题,窗口将从左上角开始向下移动,而不是交叉分解。如果我能不止一次投票,我早就做到了;)
/// 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)
    }
}
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()