"From View Controller" disappears using UIViewControllerContextTransitioning "From View Controller" disappears using UIViewControllerContextTransitioning ios ios

"From View Controller" disappears using UIViewControllerContextTransitioning


I was having the same problem here – looks like a bug in iOS 8. I've filed a radar.

I used Reveal to inspect the view hierarchy after the screen goes black. The key UIWindow is completely empty – no view hierarchy at all!

Reveal'd

I played around a bit and it looks like there is an easy workaround, for simple cases. You can just re-add the toViewController's view as a subview of the key window's:

transitionContext.completeTransition(true)UIApplication.sharedApplication().keyWindow!.addSubview(toViewController.view)

I've checked and the key window's rootViewController is still correctly set, so that's fine. I'm not sure what would happen if you presented your controller from within an already presented modal controller, so for more complex cases, you'll have to experiment around.


I feel like the reasoning behind this should be explained better.

The view disappears because you take out the presenting view controller's view out of its original location (view hierarchy), put it inside the containerView that your animator provides but never returns it back after the animation has finished. So that view controller's view is removed with its superview (containerView) from the window completely.

In iOS 7 the system always returned view controllers' views that are involved in the presentation (presenting and presented) to their original places after the transition has finished animating automatically. That no longer happens for some presentation styles in iOS 8.

The rule is very simple: the animator should only manipulate the presenting view controller's view if that view controller's view is going to be hidden (removed from the view hierarchy) completely by the end of transition. In other words it means that after the initial presentation animation finishes only the presented view controller's view will be visible and not the presenting view controller's view. For example if you set presented view controller's view's opacity to 50% and use UIModalPresentationFullScreen you will not be able to see presenting view controller's view underneath the presented but if you use UIModalPresentationOverFullscreen - you will (UIPresentationController's shouldRemovePresentersView method is responsible for specifying that).

Why not allow the animator manipulate the presenting view controller's view at all times? First of all, if the presenting view controller's view is going to stay visible after the animation finishes during the whole presentation life cycle there is no need to animate it at all — it just stays where it is. Second, if the ownership for that view controller is transferred to the presentation controller, the presentation controller will most likely not know how to layout that view controller's view when needed for example when the orientation changes, but the original owner of the presenting view controller does.

In iOS 8 viewForKey: method was introduced to get views that the animator manipulates. First, it helps to follow the rule described above by returning nil whenever the animator should not touch the view. Second, it may return a different view for the animator to animate. Imagine that you are implementing a presentation similar to form sheet. In this case you would want to add some shadow or decoration around the presented view controller's view. The animator will animate that decoration instead and the presented view controller's view will be a child of the decoration.

viewControllerForKey: doesn't go away, it can still be used if a direct access to view controllers is needed but the animator should not make any assumptions about the views it needs to animate.

There are several things you can do to correctly fix an issue with a disappearing presenting view controller's view when you explicitly place it inside the animator's container view:

  1. If do not need to animate the presenting view controller's view, use viewForKey: to get views to animate instead of reaching out to view controller's views directly. viewForKey: may return nil or even completely different views.

  2. If you want to animate the presenting view controllers's view you should consider using UIModalPresentationFullScreen style or continue using UIModalPresentationCustom and implement your own subclass of UIPresentationController with shouldRemovePresentersView returning YES. In fact, the implementation of this method is the main difference between internal presentation controllers defined by UIModalPresentationFullScreen and UIModalPresentationCustom styles apart from the fact that the latter allows you to use custom presentation controllers.

  3. In all other rare cases you will have to return the presenting view controller's view to its original location as other answers suggested.


In iOS 8, you must manipulate the views returned by viewForKey: instead of the .view property of the view controllers returned by viewControllerForKey:. This isn't particularly clear from the beta documentation, but if you look into the source for UIViewControllerTransitioning.h you'll see this comment above viewControllerForKey::

// Currently only two keys are defined by the// system - UITransitionContextToViewControllerKey, and// UITransitionContextFromViewControllerKey.// Animators should not directly manipulate a view controller's views and should// use viewForKey: to get views instead.- (UIViewController *)viewControllerForKey:(NSString *)key;

So instead of adjusting frames etc of toViewController.view, use the return value of [transitionContext viewForKey:UITransitionContextToViewKey].

If your app needs to support iOS7 and/or Xcode 5, then you can use a simple category method on UIViewController like the following:

- (UIView *)viewForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext{#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {        NSString *key = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] == self ? UITransitionContextFromViewKey : UITransitionContextToViewKey;        return [transitionContext viewForKey:key];    } else {        return self.view;    }#else    return self.view;#endif}

Then, get your toViewController and fromViewController as usual, but get the views using [toViewController viewForTransitionContext:transitionContext].

Edit: There appears to be a bug, where the presenting view controller's view is nil when returned from viewForKey, which prevents you from making modal transitions that animate the presenting view at all (such as sliding off, or flip-horizontal). I filed a bug for iOS8 at rdar://17961976 (http://openradar.appspot.com/radar?id=5210815787433984). Also see the sample project at http://github.com/bcherry/TransitionBug

Edit 2: Thanks to graveley for the suggestion, using UIModalPresentationFullScreen fixes the issue. Perhaps this is not a bug. Apple may intend that UIModalPresentationCustom only modifies the view of the incoming modal. If you want to modify the outgoing view, you need to guarantee full screen presentation of the new view? In any case, you should use viewForKey and UIModalPresentationFullScreen.