Since iOS 16, a sheet becomes a bottom sheet by attaching presentationDetents to the sheet's content. The API covers sizing, programmatic resize, drag indicator, background, and interaction behavior, so hand-rolled bottom sheets are rarely needed.
Detents Set the Sizes
One detent fixes the size; several let the user drag between them. .medium is half the screen.
.sheet(isPresented: $sheetShown) {
NavigationStack {
Text("Your query: \(query)")
.searchable(text: $query)
.navigationTitle("Search")
}
.presentationDetents([.medium, .large])
}
.fraction(0.2) sizes by a fraction of available space, .height(300) by points, and the options mix freely in one array.
Custom Detents Read the Environment
Conform to CustomPresentationDetent for sizing logic that depends on context. The Context exposes environment values, such as the dynamic type size.
struct MyCustomDetent: CustomPresentationDetent {
static func height(in context: Context) -> CGFloat? {
if context.dynamicTypeSize.isAccessibilitySize {
return context.maxDetentValue
} else {
return context.maxDetentValue * 0.8
}
}
}
.presentationDetents([.medium, .custom(MyCustomDetent.self)])
Programmatic Resize
Pass a selection binding and the sheet follows it, in both directions: dragging updates the binding, and setting the binding resizes the sheet.
@State private var sheetSize: PresentationDetent = .medium
.presentationDetents([.medium, .large], selection: $sheetSize)
Appearance and Interaction Modifiers
Each of these attaches to the sheet content, next to presentationDetents:
.presentationDragIndicator(.hidden)
.presentationCornerRadius(16)
.presentationBackground(Material.ultraThin)
.presentationContentInteraction(.scrolls)
.presentationBackgroundInteraction(.enabled(upThrough: .large))
Gotchas worth knowing: when the sheet holds a scroll view, the first drag resizes the sheet by default, so set .presentationContentInteraction(.scrolls) to scroll first. The content behind a sheet is blocked by default; presentationBackgroundInteraction re-enables it up to a chosen detent, the map-app pattern. presentationBackground also takes a ViewBuilder closure for a fully custom background view.