After a solid iOS developer?

I am an iOS developer / contractor based in Sydney, Australia.
I specialise in building well-structured app platforms for companies to build their long-term app strategies upon.
I have a broad array of experience from my work at Google, News Corp, Fox Sports, NineMSN, FetchTV, Woolworths, and Westpac.
Please see my portfolio, then get in touch if I can be of service!

See my Portfolio »

Transitioning bugs

Hi all, I struggled half of today on a bug in iOS8 when creating custom view controller transitions. Two of my friends shortly afterwards told me they had the same problem. In the hope that I can help someone out there not waste half a day on this problem, here we go:

The problem

Say you've got a View Controller, and you want to display it modally, but you want a more interesting transition than the normal 'swoosh up, perform some action, tap close, and it swooshes down'. So you read the documentation, set modalPresentationStyle to Custom. Next, you set the modal VC's transitioningDelegate to some class that implements UIViewControllerTransitioningDelegate (a reasonable choice is that your modal VC will be its own transitioning delegate). Then, on this delegate class, you implement animationControllerForPresentedController... and animationControllerForDismissedController... to return your presentation and dismissal transitions respectively. And then you implement those two transitions, following the basic rules: Add the 'to' view to the 'container' view at some stage, and call the context's completeTransition when you're done.

So far, so good, nothing overly complicated there (it sounds harder than it actually is). And for me, running it on iOS7 bears no surprises, it works nicely. However, on iOS8...

...on iOS8, on the dismissal transition, the 'to' view controller is removed from the main window at the end of the transition. This is an issue that has even tripped up geniuses such as Ash Furrow; His radar for this issue.

So I had to come up with a solution that would work on iOS7, iOS8, and (fingers crossed) on iOS9+ regardless of whether they fix the bug or not.

Solution

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    // Get the 'from' and 'to' views/controllers.
    UIViewController *fromVC = [transitionContext viewControllerForKey:
        UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:
        UITransitionContextToViewControllerKey];
    // viewForKey is only available on iOS8+.
    BOOL hasViewForKey = [transitionContext
        respondsToSelector:@selector(viewForKey:)];
    UIView *fromView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextFromViewKey] :
        fromVC.view;
    UIView *toView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextToViewKey] :
        toVC.view;
    UIView *container = [transitionContext containerView];

    // iOS8 has a bug where viewForKey:to returns nil.
    // The workaround is:
    // A) get the 'toView' from 'toVC'.
    // B) manually add the 'toView' to the container's
    // superview (eg the root window) after the completeTransition
    // call, as automatically happens on iOS7 where things work properly.
    BOOL toViewNilBug = toView==nil;
    if (!toView) { // Workaround by getting it from the view.
        toView = toVC.view;
    }
    UIView *containerSuper = container.superview;

    // Perform the transition.
    toView.frame = container.bounds;
    toView.alpha = 0;
    [container addSubView:toView];
    [UIView animateWithDuration:kDuration delay:0
        options:UIViewAnimationOptionCurveEaseIn animations:^{

        toView.alpha = 1;

    } completion:^(BOOL finished) {

        [transitionContext completeTransition:YES];

        if (toViewNilBug) {
            [containerSuper addSubview:toView];
        }

    }];
}

Explanation

I found that viewForKey:UITransitionContextToViewKey returns nil on iOS8. So if it's nil, I grab the view from the 'to' view controller.

However, this bug also seems to result in the 'to' view not being moved from the container to the window when completeTransition:YES is called. On iOS7, before completeTransition is called, the 'to' view is a subview of the container; then completeTransition moves it up a level to be a subview of the container's superview (which happens to be the UIWindow).

So if viewForKey:UITransitionContextToViewKey returns nil, I fail over to toVC.view and keep track of the fact that it hit the bug. Then immediately after the completeTransition call, if the bug was hit, I add it to the container's initial superview (which happens to be the UIWindow), to mimic the iOS7 behaviour.

And we need to store the container's original superview in the containerSuper variable, because container.superview returns nil after the completeTransition call. This is because the container is just temporary, and is correctly removed at the end of the transition by UIKit.

So this code works on iOS7 as well as iOS8, and should work on iOS9 regardless of whether they fix this bug or not. But don't hold me to it.

Thanks for reading

If you're interested, I recently released a video series on making an iOS app - please check it out: splinter.com.au/videos

I'd love to get an email if this helps you!


You can read more of my blog here in my blog archive.