SwiftUI’s sheets are used to present new view controllers modally over existing ones. Let's cover how to implement it simply and concise.
SwiftUI’s sheets are used to present new view controllers modally over existing ones. To use one, give it something to show (some text, an image, a custom view, etc), add a Boolean that defines whether the detail view should be showing, then attach it to your main view as a modal sheet.
For iOS 15 and above
Present a new view
struct SheetView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button("Press to dismiss") {
dismiss()
}
.font(.title)
.padding()
.background(Color.black)
}
}
struct ContentView: View {
@State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
}
Full Screen Sheet
SwiftUI’s fullScreenCover()
modifier gives us a presentation style for times when you want to cover as much of the screen as possible, and in code it works almost identically to regular sheets.
For example, this will present a full screen modal view when the button is pressed:
import SwiftUI
struct ContentView: View {
@State private var isPresented = false
var body: some View {
Button("Present!") {
self.isPresented.toggle()
}
.fullScreenCover(isPresented: $isPresented, content: FullScreenModalView.init)
}
}
struct FullScreenModalView: View {
// Needed to keep track that a sheet was presented
@Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
HStack {
Button(action: {}) {
Text("Cancel")
.foregroundColor(Color.blue)
.padding(.leading, 30)
.padding(.top, 10)
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
Spacer()
}
Spacer()
VStack {
Text("This is a modal view")
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color("Bg").ignoresSafeArea(.all, edges: .all)) // Set your custom color here, or not :D
}
}
Half Sheet Modal
Demo

You can customize whether it goes all the way up or not in presentationController.detents
, for example, just leave medium()
// Custom Half Sheet Modifier....
extension View{
// Binding Show Variable...
func halfSheet<SheetView: View>(showSheet: Binding<Bool>,@ViewBuilder sheetView: @escaping ()->SheetView,onEnd: @escaping ()->())->some View{
// why we using overlay or background...
// bcz it will automatically use the swiftui frame Size only....
return self
.background(
HalfSheetHelper(sheetView: sheetView(),showSheet: showSheet, onEnd: onEnd)
)
.onChange(of: showSheet.wrappedValue) { newValue in
if !newValue{
onEnd()
}
}
}
}
// UIKit Integration...
struct HalfSheetHelper<SheetView: View>: UIViewControllerRepresentable{
var sheetView: SheetView
@Binding var showSheet: Bool
var onEnd: ()->()
let controller = UIViewController()
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
func makeUIViewController(context: Context) -> UIViewController {
controller.view.backgroundColor = .clear
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
if showSheet{
if uiViewController.presentedViewController == nil{
// presenting Modal View....
let sheetController = CustomHostingController(rootView: sheetView)
sheetController.presentationController?.delegate = context.coordinator
uiViewController.present(sheetController, animated: true)
}
}
else{
// closing view when showSheet toggled again...
if uiViewController.presentedViewController != nil{
uiViewController.dismiss(animated: true)
}
}
}
// On Dismiss...
class Coordinator: NSObject,UISheetPresentationControllerDelegate{
var parent: HalfSheetHelper
init(parent: HalfSheetHelper) {
self.parent = parent
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
parent.showSheet = false
}
}
}
// Custom UIHostingController for halfSheet....
class CustomHostingController<Content: View>: UIHostingController<Content>{
override func viewDidLoad() {
// setting presentation controller properties...
if let presentationController = presentationController as? UISheetPresentationController{
presentationController.detents = [
.medium()
// .large()
]
// to show grab protion...
presentationController.prefersGrabberVisible = true
}
}
}
// View:
struct ShowSheet: View{
@State var showSheet: Bool = false
var body: some View{
Button {
showSheet.toggle()
} label: {
Text("Present Sheet")
}
.halfSheet(showSheet: $showSheet) {
SheetView()
} onEnd: {
print("Dismissed")
}
}
}
Prevent the dismissable of forms and sheets
There’s an interactiveDismissDisabled
function where the developer can choose to prevent the dismissable of forms and sheets. Here’s a quick illustration:
