Complete Guide to Modal Presentations

SwiftUI offers multiple modal presentation styles. Here's when to use each and how they work together.
Follow along with the code: iOS-Practice on GitHub
Presentation Types
| Type | Use Case |
|---|---|
.sheet | Detail views, forms, settings |
.fullScreenCover | Immersive content, onboarding |
.alert | Simple confirmations, messages |
.confirmationDialog | Destructive actions, choices |
Sheet Presentation
Boolean-based:
@State private var showSheet = false
Button("Show") { showSheet = true }
.sheet(isPresented: $showSheet) {
MySheetView()
}
Item-based (preferred when passing data):
@State private var selectedItem: Item?
Button("Show") { selectedItem = someItem }
.sheet(item: $selectedItem) { item in
ItemDetailView(item: item)
}
Full Screen Cover
@State private var showFullScreen = false
.fullScreenCover(isPresented: $showFullScreen) {
OnboardingView()
}
No drag-to-dismiss gesture—must provide explicit dismiss.
Alerts
@State private var showAlert = false
@State private var alertMessage = ""
.alert("Success", isPresented: $showAlert) {
Button("OK") {}
} message: {
Text(alertMessage)
}
Confirmation Dialogs
@State private var showConfirmation = false
@State private var itemToDelete: Item?
.confirmationDialog(
"Delete Item",
isPresented: $showConfirmation,
presenting: itemToDelete
) { item in
Button("Delete \(item.name)", role: .destructive) {
deleteItem(item)
}
Button("Cancel", role: .cancel) {}
} message: { item in
Text("Are you sure you want to delete \(item.name)?")
}
Swipe Actions
ForEach(products) { product in
ProductRow(product: product)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
itemToDelete = product
showConfirmation = true
} label: {
Label("Delete", systemImage: "trash")
}
}
}
Passing Data Back
Use a closure callback:
struct AddProductSheet: View {
@Environment(\.dismiss) private var dismiss
@State private var productName = ""
let onAdd: (String) -> Void
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $productName)
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
onAdd(productName)
dismiss()
}
.disabled(productName.isEmpty)
}
}
}
}
}
// Usage
.sheet(isPresented: $showAddSheet) {
AddProductSheet { newName in
// Handle the new product name
products.append(Product(name: newName, ...))
}
}
Dismiss Environment
@Environment(\.dismiss) private var dismiss
Button("Close") {
dismiss()
}
Works in sheets, full-screen covers, and navigation destinations.
Interview Tip
Discuss when to use each presentation type. Sheets for content, alerts for simple messages, confirmation dialogs for destructive actions.