The Problem:
In iOS 16.4’s SwiftUI, the ".sheet" modifier’s ".presentationDetents" and ".presentationBackgroundInteraction" are occasionally ignored. This behavior is inconsistent–sometimes presenting as a full-height modal with no background interaction when changing selections. The bug doesn’t occur when hiding the sheet before selecting another choice, and it’s not limited to a specific view, so it’s not a PickerView issue. The root cause is assumed to be the sheet’s selection not passing through the nil state. The workaround is to hide the sheet, change your selection, then show the sheet again.
The Solutions:
Solution 1: Use ‘sheet(isPresented:)’ if ‘sheet(item:)’ causes a bug
- Instead of using
.sheet(item:)
, consider using.sheet(isPresented:)
. - This way, the sheet will remain visible as long as the
isPresented
binding is true, regardless of the selected item. - To achieve this, you can create an additional
@State
variable,sheetItem
, to track the currently selected item and bindsheetItem
tosheet(isPresented:)
. - When the
selection
changes, assignnil
tosheetItem
and then, usingDispatchQueue.main.async
, assign the new selection tosheetItem
. - This will cause the sheet to disappear briefly before reappearing with the new selection.
Code Example:
struct ContentView: View {
@State var selection: String?
@State var sheetItem: String?
var body: some View {
VStack {
Picker("Choose", selection: $selection) {
ForEach(choices, id: \.self) { choice in
Text(choice ?? "None")
.tag(choice)
}
}
.padding()
Spacer()
}
.pickerStyle(.segmented)
.sheet(item: $sheetItem) { choice in
Text("Item selected: \(choice)")
.presentationDetents([.medium])
.presentationBackgroundInteraction(.enabled(upThrough: .medium))
}
}
func isNotNil<T>(_ binding: Binding<T?>) -> Binding<Bool> {
Binding {
binding.wrappedValue != nil
} set: { notNil in
if !notNil {
binding.wrappedValue = nil
}
}
}
private let choices = [nil, "One", "Two", "Three"]
}
In this example:
- The sheet will no longer disappear and reappear when the
selection
changes. - The sheet will remain visible as long as a selection is made.
- When the
selection
changes, the sheet will update to display the newly selected item.
Solution 2: Use `.id()` to Specify a Different Sheet
To work around the bug where the bottom sheet sometimes ignores the `.presentationDetents` and `.presentationBackground Interaction`, add the `.id()` modifier to the sheet. This lets the system know that the sheet you are presening is different from the previous one, so it can assign the `.presentationDetents` property again.
.sheet(item: $selection) {choice in
Text("Item selected: \(choice)")
.id(choice) // Add the .id() modifier
.presentationDetents([.medium])
.presentationBackground Interaction(
.enabled(upThrough: .medium)
)
}
With this added `.id()` modifier, the system will recognize the sheet as a new instance and correctly assign the `.presentationDetents` property, ensuring that the sheet is presented with the desired height and background interaction.
Q&A
From iOS 16.4 in SwiftUI we can use the modifier with to specify the height of a bottom sheet. But sometimes it ignores and just shows as a full height modal without any background interaction. How to fix it?
Use sheet(isPresented:)
instead of sheet(item:)
. Show the sheet as long as the selection is not nil.
What is suggested to be used instead of the sheet(item:)
modifier?
To avoid disappearing and reappearing of the sheet use sheet(isPresented:)
.
In the code, what modifier should be added to the sheet
to fix the issue?
Add the .id()
modifier to let SwiftUI know the sheet you are presenting is different from the previous one.