iOS 13
sheet detection
app development
user interface
iOS programming

Detecting sheet was dismissed on iOS 13

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

On iOS 13, many modal presentations became card-like sheets that users can dismiss with a swipe. That changed an important assumption: dismissal is no longer always controlled by a button that you own. If you need to know when the sheet was dismissed, the right tool is usually UIAdaptivePresentationControllerDelegate, not only viewDidDisappear.

Why iOS 13 Made This Matter

Before iOS 13, many modals were full-screen by default, so dismissal was often entirely under your control. With sheet presentation styles, users can drag the sheet down interactively. That means your code needs a callback that fires when the system completes that dismissal.

Use presentationControllerDidDismiss

The most direct callback is presentationControllerDidDismiss(_:).

Example in Swift:

swift
1import UIKit
2
3final class EditProfileViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6        presentationController?.delegate = self
7    }
8
9    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
10        print("Sheet was dismissed")
11    }
12}

If the user swipes the sheet down and the dismissal completes, this delegate method is called.

That is the usual answer when the requirement is specifically “tell me when the sheet was dismissed.”

Set the Delegate on the Presented Controller

A common source of confusion is where to assign the delegate. The presented view controller usually sets:

swift
presentationController?.delegate = self

If you want the presenting controller to observe the dismissal instead, you can assign the presented controller’s presentationController?.delegate to the presenting controller after presentation setup, as long as the delegate object conforms correctly.

The key is that the delegate belongs to the UIPresentationController, not to a random view controller lifecycle event.

UIAdaptivePresentationControllerDelegate provides more than one callback. The most useful ones are:

  • 'presentationControllerShouldDismiss'
  • 'presentationControllerWillDismiss'
  • 'presentationControllerDidDismiss'
  • 'presentationControllerDidAttemptToDismiss'

Example:

swift
1func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
2    return !hasUnsavedChanges
3}
4
5func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
6    if hasUnsavedChanges {
7        showDiscardChangesAlert()
8    }
9}

This is useful when the sheet contains a form and you want to block dismissal until the user confirms.

Difference Between Programmatic and Interactive Dismissal

If you dismiss the sheet yourself with:

swift
dismiss(animated: true)

then you may also use the dismissal completion handler on the presenting side or your own custom flow logic. But for user-driven swipe dismissal, the presentation controller delegate is the more specific and reliable signal.

That is why relying only on button actions is incomplete in iOS 13 style modal sheets.

What About viewDidDisappear

viewDidDisappear can tell you the view is no longer visible, but it is less precise. A view can disappear for reasons other than a completed sheet dismissal. For example, another presentation or navigation event could also cause it.

So while this works:

swift
1override func viewDidDisappear(_ animated: Bool) {
2    super.viewDidDisappear(animated)
3    print("View disappeared")
4}

it does not answer the question as precisely as presentationControllerDidDismiss.

Use viewDidDisappear for general lifecycle work, and the presentation controller delegate when the dismissal event itself matters.

Complete Example With a Presenting Controller

swift
1import UIKit
2
3final class HostViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
4    func showSheet() {
5        let vc = EditProfileViewController()
6        vc.modalPresentationStyle = .automatic
7        vc.presentationController?.delegate = self
8        present(vc, animated: true)
9    }
10
11    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
12        refreshProfileView()
13    }
14
15    private func refreshProfileView() {
16        print("Refresh after dismissal")
17    }
18}

The exact assignment timing can vary depending on your presentation flow, but the pattern is the same: attach the delegate and react in presentationControllerDidDismiss.

Common Pitfalls

The biggest pitfall is relying only on a close button callback. On iOS 13 sheets, users can often dismiss by swiping, so button-only logic misses a real path.

Another common mistake is expecting viewWillDisappear or viewDidDisappear to mean exactly “the sheet was dismissed by the user.” Those methods are broader lifecycle signals.

People also sometimes forget to set the presentation controller delegate, then wonder why none of the sheet callbacks fire.

Finally, if you block dismissal with presentationControllerShouldDismiss, remember to handle the user’s attempted swipe with presentationControllerDidAttemptToDismiss so the UI still gives feedback.

Summary

  • For iOS 13 sheet dismissal detection, use UIAdaptivePresentationControllerDelegate.
  • 'presentationControllerDidDismiss is the key callback for completed dismissal.'
  • Set the delegate on the relevant presentationController.
  • Use presentationControllerShouldDismiss and presentationControllerDidAttemptToDismiss when dismissal may be blocked.
  • 'viewDidDisappear is broader lifecycle information, not the most precise sheet-dismissal signal.'

Course illustration
Course illustration

All Rights Reserved.