I have a custom container view controller: ContainerVC
. Its job is to present one of two content view controllers: ContentPortraitVC
or ContentLandscapeVC
, depending on the current orientation (though it doesn't matter why the container chooses its view, I presume). ContentPortraitVC
, at some point pops up ContentModalDetailVC
.
So there are two different methods of displaying new content at work here:
the parent-and-child relationship (instigated via
addChildViewController
and removed viaremoveFromParentViewController
),the presenting-and-presented relationship (instigated via
presentViewController
and removed viadismissViewController
).
If the ContainerVC
adds the ContentPortraitVC
, which then presents the ContentModalDetailVC
, and then the ContainerVC
decides to switch to the ContentLandscapeVC
, the ContentModalDetailVC
stays visible (why is it not removed when its parent is removed?)
But then, when the ContentPortraitVC
is asked to remove the ContentModalDetailVC
, nothing happens. The modal display stays put. What is going on?
When you use
addChildViewController
to add theContentPortraitVC
:a. The
ContentPortraitVC
gets itsparentViewController
property set.b. You then (as per the Apple documentation) have to manually display the
ContentPortraitVC
's view. If you follow the documentation you do this by adding it as a child of theControllerVC
's top level view.The
ContentPortraitVC
then callspresentViewController
to displayContentModalDetailVC
.a. This sets its
presentingViewController
property (in the debugger this is shown as the_parentModalViewController
ivar -- note the ivar is different from the property), and sets thepresentedModalViewController
property of theContentPortraitVC
(who's ivar is_childModalViewcontroller
).b. Views wise, on iPhone, the
ContentModalDetailVC
's view will completely replace the views fromContentPortraitVC
andContainerVC
, so only the modal view controller's view will be visible. (on iPad, it layers the new UI over the top, but as a sibling of theControllerVC
's view, which in turn is the parent ofContentPortraitVC
's view).So now, you transition from
ContentPortraitVC
toContentLandscapeVC
.a. IOS does a bit of magic. It knows that the thing you are removing (
ContentPortraitVC
) has apresentedViewController
currently active, so it changes its parent. It sets the value tonil
onContentPortraitVC
, takes the child (theContentModalDetailVC
) and sets its parent to the new view (ContentLandscapeVC
). So now the view controller that presented the modal view is no longer its presenting view controller. It is as ifContentLandscapeVC
presented it in the first place!b. In terms of views, you follow the Apple docs to change over the view from
ContentPortraitVC
toContentLandscapeVC
. But you are simply changing the subviews ofControllerVC
's view. On iPhone, the modal view controller is still the only thing being displayed, so making the change doesn't do anything on screen. On iPad, it does (though you probably won't see it, as the modal view is usually full screen).Now you come to dismiss the modal view. Presumably you do this in
ContentPortraitVC
, but it no longer has any reference to the thing it presented. So calling[self dismissViewController...
does nothing, becauseContentPortraitVC
is no longer presenting anything, responsibility for that has been passed on toContentLandscapeVC
.So that's what happens and why. Here's what to do about it.
You can rewire the delegate manually when you change from
ContentPortraitVC
toContentLandscapeVC
, so the latter is the one that tries to dismiss the modal controller.You can have the modal controller dismiss itself with
[self dismissModalControllerAnimated:YES completion:nil]
. I'm going to ask and answer another question on why that works (how does IOS know which to dismiss?), if that seems strange.You can have the
ControllerVC
be the one that pops up the modal view and be responsible for removing it.If you inspect
presentingViewController
onContentModalDetailVC
, you will see that it is actually presented byContainerVC
and notContentPortraitVC
.To fix this, you just need to set
definesPresentationContext
(or use the "Defines Context" checkbox in Interface Builder) onContentPortraitVC
.This will tell
ContentPortraitVC
to handle the modal presentation instead of passing up the responder chain to the next view controller that defines presentation context (your root view controller by default).You will probably want
ContentLandscapeVC
to define context as well to avoid the same issue.With both child controllers defining their own presentation context, when
ContainerVC
decides to swap children, any modal modal will be removed from the new hierarchy along with the child that presented it. No need to do hacky things to try to dismiss before swapping :)Edit: I should add that the view controller being presented must have its
modalPresentationStyle
Set to eithercurrentContext
oroverCurrentContext
,