How does modal presentation work, and how do the presentation styles and sheets differ?

7.3.3 · L3 · Interview

Predict first

What are the defaults for dismissal blocking and a medium detent?

import UIKit

print(UIViewController().isModalInPresentation, UISheetPresentationController.Detent.medium().identifier.rawValue)
  • false com.apple.UIKit.medium
  • true medium
  • false large

Mental model. A fullscreen modal is a curtain that replaces the stage. A sheet is a card laid over the stage, which stays in place underneath.

Presenting is present(_:animated:completion:) on a controller, which becomes the presenting controller; the new one is the presented controller (presentedViewController / presentingViewController). The modalPresentationStyle decides what the user sees: .fullScreen covers everything and removes the presenter’s view; .pageSheet and .formSheet show a card (the default .automatic maps to a sheet on iPhone for most controllers); .overFullScreen covers everything but keeps the presenter’s view in the hierarchy so it shows through transparency (for dimmed overlays); .popover anchors to a view or bar item and adapts on compact width; .custom uses your own UIPresentationController. Sheets can use detents (.medium(), .large(), custom) through sheetPresentationController, plus a grabber and a largest-undimmed detent.

Behaviours worth knowing: a presented controller is dismissed with dismiss (from itself or the presenter), and isModalInPresentation (false by default) blocks swipe-to-dismiss, for example when there are unsaved changes, with presentationControllerDidAttemptToDismiss to react. definesPresentationContext and providesPresentationContextTransitionStyle control which controller a .currentContext or .overCurrentContext presentation is relative to. A presenter that is already presenting cannot present again: present from the topmost presented controller. And a presented controller’s lifecycle for .fullScreen removes the presenter’s did disappear, while sheets do not, so do not rely on viewWillAppear to refresh after a sheet closes.

Interactive dismissal is allowed by default, and the built-in medium detent has a fixed system identifier.

Common traps

  • Presenting from a controller that is already presenting. It fails with a warning. Present from the top-most one.
  • Refreshing the presenter in viewWillAppear after a sheet. It is not called for a sheet, so use a delegate or completion callback.

They might follow up with

  • How do you make a half-height sheet? Set sheetPresentationController?.detents = [.medium(), .large()] before presenting.
  • What does isModalInPresentation do? It stops interactive dismissal; use it to guard unsaved edits.

Related questions

  • 7.3.1 What is the view controller lifecycle, and what belongs in each method?
  • 7.3.4 How do you build a custom view controller transition, including an interactive one?

← What is view controller containment, and what is the correct way to…   How do you build a custom view controller transition, including an… →