使用带有子类 UINavigationController 的自定义 iOS 7 过渡偶尔会导致黑屏

Posted

技术标签:

【中文标题】使用带有子类 UINavigationController 的自定义 iOS 7 过渡偶尔会导致黑屏【英文标题】:Using custom iOS 7 transition with subclassed UINavigationController occasionally results in black screen 【发布时间】:2014-08-26 18:58:47 【问题描述】:

我将UINavigationController 子类化为我的应用程序的主根控制器。视图控制器的目的是呈现一个自定义启动屏幕,允许从(从右到左)滑动操作进入主应用程序。自定义转换在滑动期间淡化启动视图并显示下面的主应用程序(UITabBarController)。我的大部分代码都遵循 objc.io 关于 ios7 中自定义视图控制器转换的文章:http://www.objc.io/issue-5/view-controller-transitions.html

一切似乎都按预期工作。但是,很少有人报告说,一旦启动画面消失,就会出现黑屏,而不是主屏幕。我一直无法重现该行为。代码如下:

首先,在自定义应用委托中设置主根视图控制器:

- (void)applicationDidFinishLaunching:(UIApplication *)application 

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.rootViewController = [[CustomRootViewController alloc] initWithNavigationBarClass:nil toolbarClass:nil];
    self.window.rootViewController = self.rootViewController;
    [self.window makeKeyAndVisible];

来自CustomRootViewController 的重要部分(注意我隐藏了导航栏):

- (instancetype) initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass

    self = [super initWithNavigationBarClass:navigationBarClass toolbarClass:toolbarClass];
    if (self) 
        self.navigationBarHidden = YES;
    
    return self;


- (void)viewDidLoad

    [super viewDidLoad];

    self.delegate = self;

    // This is the main UI for the app
    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
    CustomTabBarControllerViewController *mainViewController = [mainStoryboard instantiateInitialViewController];
    self.mainViewController = mainViewController;

    UIStoryboard *splashStoryboard = [UIStoryboard storyboardWithName:@"Splash" bundle:[NSBundle mainBundle]];
    SplashViewController *splashViewController = [splashStoryboard instantiateInitialViewController];
    self.splashViewController = splashViewController;

    // Initialize the navigation controller to have the main app sitting under the splash screen
    self.viewControllers = @[self.mainViewController, self.splashViewController];


// In the public interface, called from the Splash screen when the user can perform the action
- (void)allowSwipeToDismiss

    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
    panGestureRecognizer.delegate = self;
    [self.view addGestureRecognizer:panGestureRecognizer];


- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController *)fromVC
                                                 toViewController:(UIViewController *)toVC

    if (operation == UINavigationControllerOperationPop) 
        return [[CustomRootViewControllerAnimator alloc ] init];
    

    return nil;


- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                         interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController

    return self.interactiveController;


- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

    // Only enable swiping when on the Swipe To Enter screen
    // After we are in the main app, we don't want this gesture recognizer interfering with the rest of the app
    if (self.topViewController == self.splashViewController) 
        return true;
     else 
        return false;
    


- (void)handlePan:(UIPanGestureRecognizer *)recognizer

    if (recognizer.state == UIGestureRecognizerStateBegan) 
        self.interactiveController = [[UIPercentDrivenInteractiveTransition alloc] init];
        [self popViewControllerAnimated:YES];
     else if (recognizer.state == UIGestureRecognizerStateChanged) 
        CGPoint translation = [recognizer translationInView:self.view];
        CGFloat viewWidth = self.view.bounds.size.width;
        CGFloat percentDone = -translation.x / viewWidth;
        [self.interactiveController updateInteractiveTransition:percentDone];
     else if (recognizer.state == UIGestureRecognizerStateEnded) 
        if (self.interactiveController.percentComplete < 0.5) 
            [self.interactiveController cancelInteractiveTransition];
         else 
            [self.interactiveController finishInteractiveTransition];
        
        self.interactiveController = nil;
    

这是我的自定义 UINavigationController 返回的 CustomRootViewControllerAnimator 的代码:

@implementation CustomRootViewControllerAnimator

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext

    return 1;


- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext

    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

    [[transitionContext containerView] insertSubview:toViewController.view
                                        belowSubview:fromViewController.view];

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^
        CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
        fromViewController.view.transform = CGAffineTransformMakeTranslation(-screenWidth, 0);
        fromViewController.view.alpha = 0;
     completion:^(BOOL finished) 
        fromViewController.view.transform = CGAffineTransformIdentity;
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    ];


@end

【问题讨论】:

【参考方案1】:

终于能够重现该问题,但仅限于 iOS 7.0。通过启动交互式过渡,然后取消它,我能够在下一次过渡开始时获得黑屏。在完成块中,我需要将 fromViewController 的 alpha 值设置回 1。同样,这仅在 iOS 7.0 中是必需的。它不会在 7.1 中发生。

【讨论】:

以上是关于使用带有子类 UINavigationController 的自定义 iOS 7 过渡偶尔会导致黑屏的主要内容,如果未能解决你的问题,请参考以下文章

使用 R 和 Shiny 的带有子类别的下拉菜单

以编程方式使用自动布局实现带有子视图的 uiview 子类

带有使用 QAbstractTableModel 子类的 Qt 小部件的 MVC

使用带有子类 UINavigationController 的自定义 iOS 7 过渡偶尔会导致黑屏

带有 Xib 文件的 UIView 的子类

带有 XMLParser 的 AFNetworking HTTPClient 子类