SwiftUI Patterns

Sheet-Based Detail Views

May 28, 2026
5 min read
Featured image for blog post: Sheet-Based Detail Views

Sheets are the standard way to show detail views in SwiftUI. Here's how to present them from grid or list selections.

Follow along with the code: iOS-Practice on GitHub

Item-Based Sheet Presentation

@State private var selectedPhoto: Photo?

.sheet(item: $selectedPhoto) { photo in
    PhotoDetailSheet(photo: photo)
}

When selectedPhoto changes from nil to a value, the sheet appears. When dismissed, it's automatically set back to nil.

The Detail Sheet

struct PhotoDetailSheet: View {
    let photo: Photo
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        NavigationStack {
            VStack(spacing: 20) {
                RoundedRectangle(cornerRadius: 16)
                    .fill(Color(named: photo.thumbnailColor))
                    .aspectRatio(4/3, contentMode: .fit)
                    .padding()

                Text(photo.title)
                    .font(.title2)
                    .fontWeight(.bold)

                Label(photo.category, systemImage: "folder")
                    .foregroundColor(.secondary)

                Spacer()
            }
            .navigationTitle("Photo Detail")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Done") {
                        dismiss()
                    }
                }
            }
        }
    }
}

Environment Dismiss

@Environment(\.dismiss) private var dismiss

Button("Done") {
    dismiss()
}

The dismiss action closes the sheet and sets the binding to nil.

Sheet vs Navigation

Use sheets for:

  • Detail views that don't lead deeper
  • Modal actions (create, edit)
  • Settings/preferences

Use NavigationLink for:

  • Drill-down hierarchies
  • Master-detail flows
  • Progressive disclosure

Connecting to Grid

ForEach(filteredPhotos) { photo in
    PhotoGridCell(photo: photo)
        .onTapGesture {
            selectedPhoto = photo
        }
}

Tapping a cell sets selectedPhoto, which triggers the sheet.

Sheet Sizes

Control presentation size on iPad:

.sheet(item: $selectedPhoto) { photo in
    PhotoDetailSheet(photo: photo)
        .presentationDetents([.medium, .large])
}

Boolean-Based Sheets

For simple sheets without data:

@State private var showSettings = false

Button("Settings") {
    showSettings = true
}
.sheet(isPresented: $showSettings) {
    SettingsView()
}

Preventing Dismissal

For forms that need completion:

.interactiveDismissDisabled(hasUnsavedChanges)

Full-Screen Alternative

.fullScreenCover(item: $selectedPhoto) { photo in
    PhotoDetailView(photo: photo)
}

Full-screen covers don't have the drag-to-dismiss gesture.

Interview Tip

.sheet(item:) is preferred over .sheet(isPresented:) when you need to pass data. It's cleaner and avoids force-unwrapping optionals.

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