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

时间:2016-06-30 06:54:23

标签: ios objective-c animation uitabbarcontroller

我有这个代码在UITabControllers之间进行翻转过渡:

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

然而,问题在于我正在将另一个视图控制器推入堆栈,这可以轻松地在内存中运行。如何在没有标签栏搞乱动画的情况下创建新的导航堆栈?

1 个答案:

答案 0 :(得分:0)

rootViewController不是一个非常可动画的属性,你可以想象。 makeKeyAndVisible也不可动画,你应该在运行任何动画之前执行此操作。

这个API本身已经很老了(iOS 4.0),我个人认为它更像是UIKit的遗产,我还没有看到它在iOS 6中被用来翻转视图时仍然是一件事。

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

所有这些API都旨在与容器中的兄弟视图一起使用。这是文档及其示例中提到的内容。似乎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也存在一些怪癖,例如,如果您在应用程序不在屏幕上时使用它,或者您在当前不可见的窗口上运行它,那么它将只是吞下该调用。我曾经有一张像

这样的支票
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