SwiftUI Blog

Mastering SwiftUI: Your Guide to Building Beautiful, Intuitive Apps.

Sheet

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: