Preventing Popovers on Popovers
Pages on iOS 9 shows an activity view controller in a way we can’t reproduce, and UIKit’s behavior when presenting action sheets and activity view controllers from inside form sheets and popovers seems inconsistent at first. We filed two Radars with Apple: rdar://27448912 Can’t show activity view controller filling a form sheet and rdar://27448488 Reading an alert controller’s popoverPresentationController property changes behavior.
The iOS Human Interface Guidelines state:
Don’t display a modal view above a popover. With the possible exception of an alert, nothing should appear over a popover. In rare cases when you need to present a modal view after action is taken in a popover, close the popover before displaying the modal view.
Show one popover at a time. Displaying multiple popovers clutters the interface and causes confusion. Never show a cascade or hierarchy of popovers, in which one emerges from another. If you need to show a new popover, close the open one first.
View controller presentations with the
popover style appear as popovers in horizontally regular environments and full screen in horizontally compact environments.
UIAlertController with the action sheet style follow similar rules: appearing as either popovers or a slide up sheet. So what actually happens if a popover presents an activity view controller or an action sheet? The Human Interface Guidelines and documentation seem to be in conflict.
On a related note, in Pages on iOS 9 we noticed a view controller in a form sheet presenting a
UIActivityViewController that fills the form sheet, and wondered if this was default behavior we never noticed before, or whether it was something we could achieve with customization.
With most view controllers, to present inside a popover or form sheet you set the
modalPresentationStyle of the presented view controller to
overCurrentContext. But some UIKit-provided view controllers such as
UIAlertController supply their own presentation styles, and changes to
modalPresentationStyle are ignored.
UIActivityViewController is presented as a popover in regular widths, and as a transparent sheet in compact widths. But what happens if the activity view controller is presented from a view controller in a compact width, that is within a regular width? This happens if a view controller is presented on iPad with the
modalPresentationStyle, or with a custom presentation controller using the
overrideTraitCollection property, and then that view controller presents a
First let’s look at
UIAlertController. The root view controller (cyan) presents the second view controller (pink) using the
formSheet style (on top) and with the
popover style (below, with split view behavior for reference). The second view controller then presents the alert controller with the action sheet style.
Although we want to present the action sheet with the sheet presentation style (rather than popover), I set the alert controller’s
popoverPresentationController.sourceRect because in the interests of separating concerns, this view controller should not make assumptions about how it is presented. It might be presented full screen in a different part of the app. The view controller should not have to know about this.
Out of interest, I tried commenting out the
popoverPresentationController setup, and to my surprise this happened:
It turns out that just reading the
popoverPresentationController property of the alert controller causes it to show as a popover even when presented from an environment with a compact width. If you want to do this, make sure you’re very sure about the context your view controller is presented in because if you try to present the alert controller in a regular width environment without setting up the popover source then UIKit will raise an exception. Remember that even if the presenting view controller is in a compact width at the time of presentation, that might change while the presentation is active.
Activity View Controller
Trying the same thing with
UIActivityViewController, and specifying the popover source information, this happens:
Unlike the behavior in Pages, I found the form sheet presents the activity view controller as a popover. The popover does present the activity view controller in sheet. This behavior is new in iOS 10. On iOS 9, it shows a popover presented from another popover.
Trying the same trick of not accessing the
popoverPresentationController results in UIKit raising an exception stating we "must provide location information for this popover."
We found the behavior very mixed when special UIKit view controllers are presented from compact width environments which are themselves presented from regular width environments. The general rule for popover presentations is that they appear as popovers in regular widths and full screen in compact widths (although current context would make more sense). Action sheets and activity view controller presentations are a bit like popover presentations, but do not follow that general rule consistently.
The actual behavior seems to follow the Human Interface Guidelines, and mostly ignores the size class of the trait collection. UIKit does not show a popover over a popover with the exception of action sheets, which are alerts. Size classes aren’t everything.
We can’t reproduce the behavior from Pages. For us, when a form sheet presents an activity view controller it appears as a popover. I reported this to Apple in rdar://27448912 Can’t show activity view controller filling a form sheet. If you know a way to do this, let me know on Twitter.