This post attempts to work around one of the bugs triggered by setting modalPresentationStyle
to Custom
. However, there are numerous other bugs caused by this:
This may sound huge, however there is one simple workaround: set the modalPresentationStyle
to FullScreen
. You still get the custom transition, and everything else just works. So, if you do that, you can ignore the rest of this post…
One more gotcha: On iOS7, the from view controller has a strong reference to your transition, so make sure that your transition references the view controllers weakly, or nil them just before calling completion, to avoid a memory leak.
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:
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.
- (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];
}
}];
}
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.
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!
Thanks for reading! And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.
(Comp Sci, Hons - UTS)
Software Developer (Freelancer / Contractor) in Australia.
I have worked at places such as Google, Cochlear, Assembly Payments, News Corp, Fox Sports, NineMSN, FetchTV, Coles, Woolworths, Trust Bank, and Westpac, among others. If you're looking for help developing an iOS app, drop me a line!
Get in touch:
[email protected]
github.com/chrishulbert
linkedin