1
votes

I am subclassing a UINavigationController to serve as the main root controller of my application. The purpose of the view controller is to present a custom splash screen that allows for a swipe action from (right to left) to enter the main application. The custom transition fades the splash view during the swipe and reveals the main application underneath (a UITabBarController). Most of my code is following the objc.io article on custom view controller transitions in iOS7: http://www.objc.io/issue-5/view-controller-transitions.html.

Everything seems to work as expected. However, very rarely, I am getting reports of a black screen appearing once the splash screen disappears, instead of the main screen. I have been unable to reproduce the behavior. Here is the code:

First, setting up the main root view controller in the custom app delegate:

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

Important portions from CustomRootViewController (note I'm hiding the navigation bar):

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

And here is the code for the CustomRootViewControllerAnimator being returned by my custom UINavigationController:

@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

1 Answers

0
votes

Was finally able to replicate the problem, but only in iOS 7.0. By starting the interactive transition, then canceling it, I was able to get the black screen on the next start of the transition. In the completion block, I needed to set the alpha value of the fromViewController back to 1. Again, this is only necessary in iOS 7.0. It does not happen in 7.1.