使用翻转过渡创建新的导航堆栈而不是推送另一个控制器

Creating a new navigation stack with a flip transition instead of pushing another controller

我用这段代码在 UITabController 之间进行翻转转换:

UIStoryboard *sb = [UIStoryboard storyboardWithName:@"OtherSb" bundle:nil];
PrimaryTabBarController *tabBarController = [sb  instantiateInitialViewController];

[UIView transitionWithView:[APP_DELEGATE window]
                  duration:0.8
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
                    [[APP_DELEGATE window] setRootViewController:tabBarController];
                    [[APP_DELEGATE window] makeKeyAndVisible];
                }
                completion:nil];

然而,奇怪的是,在翻页过渡过程中,标签栏会从屏幕底部短暂地闪烁到顶部。我能够通过执行以下操作来停止:

PrimaryTabBarController *tabBarController = [sb  instantiateInitialViewController];

tabBarController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:tabBarController animated:YES completion:nil];

然而,这个问题是我将另一个视图控制器推到堆栈上,它可以很容易地 运行 通过内存。如何创建新的导航堆栈而不让标签栏弄乱动画?

rootViewController 并不像您想象的那样可动属性。 makeKeyAndVisible 也不是可动画的,您可能应该在 运行 任何动画之前执行此操作。

这个 API 本身已经很老了 (iOS 4.0),我个人认为它更像是 UIKit 的遗留物,自 iOS 6 以来我就没见过它被使用当翻转视图仍然是一件事时。

iOS7 中引入的自定义过渡是制作任何疯狂动画的一种非常舒适的方式,但是正如您所注意到的,它创建了一个您并不总是需要的模式层次结构。

所有这些 API 都旨在与容器中的 sibling 视图一起使用。这是文档及其示例中提到的内容。而且似乎 UIWindow 不适合作为全局动画容器。

文档中的示例代码:

[UIView transitionWithView:containerView
           duration:0.2
           options:UIViewAnimationOptionTransitionFlipFromLeft
           animations:^{ [fromView removeFromSuperview]; [containerView addSubview:toView]; }
           completion:NULL];

我建议您遵循与自定义转换相同的逻辑,并首先设置虚拟根控制器,它将作为动画容器为您服务。

然后在其中添加视图或整个视图控制器,并使用

在同级视图之间添加 运行 动画
+ transitionFromView:toView:duration:options:completion:`

- transitionFromViewController:toViewController:duration:options:animations:completion:

+ transitionWithView:duration:options:animations:completion:

除此之外,还有一个有用的标志 UIViewAnimationOptionShowHideTransitionViews 会自动隐藏翻转视图以避免它在动画后闪烁或重新出现。

当动画结束时,您可以在一次调用中交换整个根控制器,用户应该不会注意到这一点。

这个 API 也有一些怪癖,例如,如果您在应用程序不在屏幕上时偶然使用它,或者您 运行 在不在屏幕上的 window 上使用它当前可见,那么它将简单地吞下呼叫。我曾经有过像

这样的支票
if(fromViewController.view.window) { 
    /* run animations */ 
} else { 
    /* swap controllers without animations */ 
}

我做了一个示例项目来演示如何使用临时容器视图进行转换

https://github.com/pronebird/FlipRootController

UIWindow 上的示例类别:

@implementation UIWindow (Transitions)

- (void)transitionToRootController:(UIViewController *)newRootController animationOptions:(UIViewAnimationOptions)options {
    // get references to controllers
    UIViewController *fromVC = self.rootViewController;
    UIViewController *toVC = newRootController;

    // setup transition view
    UIView *transitionView = [[UIView alloc] initWithFrame:self.bounds];

    // add subviews into transition view
    [transitionView addSubview:toVC.view];
    [transitionView addSubview:fromVC.view];

    // add transition view into window
    [self addSubview:transitionView];

    // flush any outstanding animations
    // UIButton may cancel transition if this method is called from touchUpInside, etc..
    [CATransaction flush];

    [UIView transitionFromView:fromVC.view
                        toView:toVC.view
                      duration:0.5
                       options:options
                    completion:^(BOOL finished) {
                        // set new root controller after animation
                        self.rootViewController = toVC;

                        // move VC's view out of transition view
                        [self addSubview:toVC.view];

                        // remove transition view
                        [transitionView removeFromSuperview];
                    }];
}

@end