An important element in iOS is the Sheet. In this post, we will cover:
- Open and close a not full-screen sheet
- Open and close a full-screen sheet
- Open and close a screen sheet with custome size
In this example i use this image Unsplash
Creating a Non-Full-Screen Sheet
Begin by creating the project and adding the image to the assets. I’ve renamed the image to background.
struct ContentView: View {
@State var isPresented = false
var body: some View {
NavigationStack {
Text("Sheet")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("+") {
isPresented = true
}.font(.largeTitle)
}
}
}
.sheet(isPresented: $isPresented, content: {
SheetUIView()
})
}
}
First, we add a ‘plus’ button to the toolbar. When this button is tapped, the variable isPresented is set to true. Once isPresented becomes true, the sheet is displayed, showing the SheetUIView.
Define the SheetUIView:
struct SheetUIView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
ScrollView {
VStack {
Image("background")
.resizable()
.aspectRatio(contentMode: .fit)
Text("The sheet")
}
}.overlay(
HStack {
Spacer()
VStack {
Button(action: {
dismiss()
}, label: {
Image(systemName: "chevron.down.circle.fill")
.font(.largeTitle)
.foregroundStyle(.white)
})
.padding(.trailing, 20)
.padding(.top, 10)
Spacer()
}
}
)
}
}
Using the Environment’s dismiss, we call DismissAction to handle the dismissal of the current view. The body of the view includes an image at the top and some simple text. While sheets are often closed with a top-down swipe gesture, this may not always be intuitive for users.
To improve clarity, we add a close button. This button is positioned in the top-right corner and triggers the dismiss() action, allowing the sheet to close programmatically.
Creating a Full-Screen Sheet:
First, let’s begin by making a small modification to the ContentView.
struct ContentView: View {
@State var isPresented = false
var body: some View {
NavigationStack {
Text("Sheet")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("+") {
isPresented = true
}.font(.largeTitle)
}
}
}
.fullScreenCover(isPresented: $isPresented){
SheetUIView()
}
}
}
We replace sheet with fullScreenCover for this implementation.
In the SheetUIView, we use ignoresSafeArea() to achieve a visually appealing effect:
The code:
struct SheetUIView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
ScrollView {
VStack {
Image("susan")
.resizable()
.aspectRatio(contentMode: .fit)
Text("The sheet")
}
}.overlay(
HStack {
Spacer()
VStack {
Button(action: {
dismiss()
}, label: {
Image(systemName: "chevron.down.circle.fill")
.font(.largeTitle)
.foregroundStyle(.white)
})
.padding(.trailing, 20)
.padding(.top, 10)
Spacer()
}
}
).ignoresSafeArea(edges: .all)
}
}
Tip: For both scenarios mentioned above, if you’re not using an image but instead a VStack within a ScrollView, it’s recommended to define the VStack with the following dimensions to ensure proper layout and scrolling behavior:
ScrollView {
VStack {
Spacer(minLength: 100)
Text("Hello")
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
......
This step is necessary to prevent the close button, defined in the overlay, from moving along with the text in the VStack. It ensures that the button remains fixed at the top-right corner of the view.
Sheet with custom size
It’s possible to create a sheet with a custom size using detents. The first value represents the initial height of the sheet, while the second value defines the maximum height the sheet can reach when the user drags it.
.presentationDetents([.medium, large])
We can also define the height proportionally to the screen:
struct ContentView: View {
@State var isPresented = false
var body: some View {
NavigationStack {
Text("ContentView")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("+") {
isPresented = true
}.font(.largeTitle)
}
}
}
.sheet(isPresented: $isPresented) {
SheetUIView()
.presentationDetents([.fraction(0.1), .height(400)])
}
}
}
To have: