iOS
modal presentation
user interface
gesture disable
mobile development

Disable gesture to pull down form/page sheet modal presentation

Master System Design with Codemia

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

Introduction

Starting with iOS 13, Apple changed the default modal presentation style to a card-like sheet that users can dismiss by swiping down. While this feels natural in many situations, there are cases where you need to prevent that gesture, for example when a user is filling out a multi-step form or confirming a destructive action. This article covers the practical ways to disable the pull-down dismiss gesture in UIKit and SwiftUI, with working code you can drop into your project.

How the Default Sheet Dismiss Works

When you call present(_:animated:completion:) on a view controller in iOS 13 and later, the system defaults to .automatic for modalPresentationStyle, which resolves to .pageSheet on most devices. The page sheet presentation includes a built-in interactive dismiss gesture: the user drags the sheet downward to close it.

Behind the scenes, UIKit attaches a UIPanGestureRecognizer to the presented view. The system's presentation controller watches for a downward drag and triggers dismissal when the velocity and distance thresholds are met.

Disabling Dismiss in UIKit

Using isModalInPresentation

The simplest and most reliable way to prevent swipe-to-dismiss is the isModalInPresentation property, introduced in iOS 13.

swift
1class FormViewController: UIViewController {
2    override func viewDidLoad() {
3        super.viewDidLoad()
4        // Prevents swipe-to-dismiss
5        isModalInPresentation = true
6    }
7}

When isModalInPresentation is set to true, the user cannot dismiss the sheet by swiping down. They also cannot tap outside the sheet to dismiss it on iPad. The only way to close the modal is through an explicit action you provide, such as a Cancel or Done button.

Presenting the View Controller

swift
1let formVC = FormViewController()
2formVC.modalPresentationStyle = .pageSheet
3
4if let sheet = formVC.sheetPresentationController {
5    sheet.detents = [.medium(), .large()]
6    sheet.prefersGrabberVisible = true
7}
8
9present(formVC, animated: true)

Even with the grabber visible, the user can still drag between detent sizes (medium and large) but cannot dismiss the sheet by pulling it all the way down. The grabber serves as a visual indicator that the sheet supports resizing, which remains functional.

Using the Presentation Controller Delegate

For more granular control, you can implement UIAdaptivePresentationControllerDelegate. This lets you conditionally prevent dismissal based on the current state of your form.

swift
1class FormViewController: UIViewController,
2    UIAdaptivePresentationControllerDelegate {
3
4    var hasUnsavedChanges = false
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8        presentationController?.delegate = self
9    }
10
11    func presentationControllerShouldDismiss(
12        _ presentationController: UIPresentationController
13    ) -> Bool {
14        return !hasUnsavedChanges
15    }
16
17    func presentationControllerDidAttemptToDismiss(
18        _ presentationController: UIPresentationController
19    ) {
20        // Show confirmation alert when user tries to dismiss
21        let alert = UIAlertController(
22            title: "Unsaved Changes",
23            message: "Do you want to discard your changes?",
24            preferredStyle: .alert
25        )
26        alert.addAction(UIAlertAction(title: "Keep Editing", style: .cancel))
27        alert.addAction(UIAlertAction(title: "Discard", style: .destructive) {
28            _ in
29            self.dismiss(animated: true)
30        })
31        present(alert, animated: true)
32    }
33}

The delegate approach is useful when you want to allow dismissal under certain conditions (for example, the form is empty) but block it when the user has entered data.

Disabling Dismiss in SwiftUI

SwiftUI provides the interactiveDismissDisabled modifier, available from iOS 15 onward.

swift
1struct ContentView: View {
2    @State private var showForm = false
3
4    var body: some View {
5        Button("Open Form") {
6            showForm = true
7        }
8        .sheet(isPresented: $showForm) {
9            FormView()
10                .interactiveDismissDisabled(true)
11        }
12    }
13}

You can also pass a boolean binding to conditionally enable or disable the gesture based on form state.

swift
1struct FormView: View {
2    @State private var name = ""
3
4    var body: some View {
5        NavigationView {
6            Form {
7                TextField("Name", text: $name)
8            }
9            .navigationTitle("New Entry")
10        }
11        .interactiveDismissDisabled(!name.isEmpty)
12    }
13}

In this example, the sheet can be dismissed freely while the text field is empty, but once the user types something, the swipe gesture is disabled.

Handling iPad and Compact Height

On iPad, page sheets and form sheets appear as centered cards rather than full-screen overlays. Setting isModalInPresentation = true also prevents dismissal by tapping the dimmed area outside the sheet, which is the typical dismiss mechanism on iPad.

When a device is in compact height (iPhone in landscape), you may want the sheet to attach to the edges of the screen. Use prefersEdgeAttachedInCompactHeight on the sheet presentation controller for that layout, and isModalInPresentation still controls the dismiss behavior regardless of this setting.

swift
1if let sheet = formVC.sheetPresentationController {
2    sheet.detents = [.large()]
3    sheet.prefersEdgeAttachedInCompactHeight = true
4}
5formVC.isModalInPresentation = true

Common Pitfalls

  • Forgetting to set the presentationController?.delegate before the view appears, which causes the delegate methods to never fire.
  • Setting isModalInPresentation in init() instead of viewDidLoad() or later. The presentation controller may not be available that early.
  • Not providing any dismiss button when you disable the gesture. Users will be stuck with no way to close the modal unless you add an explicit action.
  • Confusing isModalInPresentation (which prevents dismissal) with modalPresentationStyle (which controls how the modal looks).
  • On SwiftUI, applying interactiveDismissDisabled to the wrong view in the hierarchy. It must be applied inside the .sheet content closure, not on the presenting view.

Summary

Use isModalInPresentation = true in UIKit or .interactiveDismissDisabled(true) in SwiftUI to prevent the swipe-to-dismiss gesture on sheet modals. For conditional blocking based on form state, implement UIAdaptivePresentationControllerDelegate in UIKit or pass a boolean to interactiveDismissDisabled in SwiftUI. Always provide an explicit dismiss button so users are not trapped in a modal with no exit.


Course illustration
Course illustration

All Rights Reserved.