- Published on
Prevent Drag-to-Dismiss on SwiftUI Sheets
- Authors

- Name
- Mick MacCallum
- @0x7fs
By default, SwiftUI allows users to dismiss modal sheets by swiping down. While this is generally good UX, there are times when you want to prevent accidental dismissal, such as when the user is filling out a form or has unsaved changes.
In iOS 15 and later, SwiftUI provides the interactiveDismissDisabled(_:) modifier to control whether a sheet can be dismissed by dragging. Apply this modifier to the view that's presented in the sheet to disable the swipe-to-dismiss gesture.
Basic Example
To completely disable drag-to-dismiss on a sheet, apply the interactiveDismissDisabled(true) modifier to the content view:
struct ContentView: View {
@State private var showSheet = false
var body: some View {
Button("Show Sheet") {
showSheet = true
}
.sheet(isPresented: $showSheet) {
SheetView()
}
}
}
struct SheetView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
VStack {
Text("This sheet cannot be dismissed by dragging")
.padding()
Button("Close") {
dismiss()
}
}
.interactiveDismissDisabled(true)
}
}
In this example, users can only dismiss the sheet by tapping the "Close" button. The swipe-to-dismiss gesture is completely disabled.
Conditional Dismissal
You can also conditionally prevent dismissal based on your app's state. This is useful for scenarios like an edit screen where you want to warn the user about unsaved changes:
struct EditFormView: View {
@Environment(\.dismiss) var dismiss
@State private var name = ""
@State private var email = ""
@State private var hasUnsavedChanges = false
@State private var showDiscardAlert = false
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $name)
.onChange(of: name) { hasUnsavedChanges = true }
TextField("Email", text: $email)
.onChange(of: email) { hasUnsavedChanges = true }
}
.navigationTitle("Edit Profile")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
if hasUnsavedChanges {
showDiscardAlert = true
} else {
dismiss()
}
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
// Save changes
hasUnsavedChanges = false
dismiss()
}
}
}
.interactiveDismissDisabled(hasUnsavedChanges)
.alert("Discard changes?", isPresented: $showDiscardAlert) {
Button("Discard", role: .destructive) {
hasUnsavedChanges = false
dismiss()
}
Button("Keep Editing", role: .cancel) { }
} message: {
Text("You have unsaved changes. Are you sure you want to discard them?")
}
}
}
}
In this example, the sheet can be freely dismissed when there are no changes. Once the user starts editing, interactiveDismissDisabled(hasUnsavedChanges) prevents accidental dismissal. When the user tries to dismiss the sheet (either by swiping or tapping Cancel), they'll be prompted to confirm they want to discard their changes.
This approach gives you fine-grained control over when sheets can be dismissed, helping prevent data loss while maintaining good user experience.
