SwiftUI Patterns

Complete Guide to Modal Presentations

June 12, 2026
6 min read
Featured image for blog post: 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

TypeUse Case
.sheetDetail views, forms, settings
.fullScreenCoverImmersive content, onboarding
.alertSimple confirmations, messages
.confirmationDialogDestructive 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.

Originally published on pixelper.com

© 2026 Christopher Moore / Dead Pixel Studio

Let's work together

Professional discovery, design, and complete technical coverage for your ideas

Get in touch