Custom sheet sizes in SwiftUI

Jun 15, 2022 · Follow on Twitter and Mastodon swiftuisheetpresentation-detents

WWDC’22 introduced many amazing additions to SwiftUI, of which many will render many 3rd party libraries obsolete. One such is SwiftUI’s new support for custom sheet sizes.

Background

Until now, SwiftUI sheets have only supported a single, large size, where the parent view is pushed back and the sheet takes over most of the screen.

When UIKit added support for custom sheet sizes last year, it wasn’t added to SwiftUI. As such, custom sheets has been a popular problem for the community to solve.

We have a bunch of open-source projects for this, my own BottomSheet included. Many of these will however no longer be needed when SwiftUI gets support for custom sheet sizes.

Defining custom sheet sizes

In SwiftUI 4 and iOS 16, you’ll be able to use the new .presentationDetents view modifier to define a set of sheet sizes that a view should support:

struct MyView: View {
        
    @State private var isPresented = true

    var body: some View {
        Color.green.edgesIgnoringSafeArea(.all)
            .sheet(isPresented: $isPresented) {
                Color.red
                    .presentationDetents([
                        .height(100),   // 100 points
                        .fraction(0.2), // 20% of the available height
                        .medium,        // Takes up about half the screen
                        .large]         // The previous default size
                    )
                    .edgesIgnoringSafeArea(.all)
            }
    }
}

This is easy to use and works very well. The view gets a drag handle that move the sheet between its available sizes. The animation is very smooth and the feature satisfying to use.

If you define a single size, the handle disappears and the sheet become non-dismissable. The sheet will bounce in a delightful way when you drag it, but will not resize to other sizes.

Single-sized sheets also can’t be dismissed by swiping down the sheet. This is perfect for onboarding guides and mandatory dialogs.

Note that this don’t apply to iPad, where the sheet is still presented as a centered window.

Hiding the drag handle

If you want to support multiple sheet sizes, but still want to hide the drag handle, you can use the new .presentationDragIndicator modifier:

struct MyView: View {
        
    @State private var isPresented = true

    var body: some View {
        Color.green.edgesIgnoringSafeArea(.all)
            .sheet(isPresented: $isPresented) {
                Color.red
                    .presentationDetents([.height(100), .fraction(20), .medium, .large])
                    .presentationDragIndicator(.hidden)  // <-- 
                    .edgesIgnoringSafeArea(.all)
            }
    }
}

The sheet will still be resizeable, but the handle will be hidden. I guess this can be useful if you want to create a custom handle that looks different than the standard one.

Undimming the underlying view

In UIKit, you can define the largest undimmed presentation detent. This can be used to create static sheets that don’t dim the underlying view, such as in Apple Maps.

In SwiftUI, this is not yet possible with the native api:s. I have therefore written an article that describes how you can do this in SwiftUI as well.

Disabling interactive dismissal

If you want to support multiple sheet sizes, but still want the sheet to be non-dismissable, you can use the .interactiveDismissDisabled modifier:

struct MyView: View {
        
    @State private var isPresented = true

    var body: some View {
        Color.green.edgesIgnoringSafeArea(.all)
            .sheet(isPresented: $isPresented) {
                Color.red
                    .presentationDetents([.height(100), .fraction(20), .medium, .large])
                    .interactiveDismissDisabled()  // <-- 
                    .edgesIgnoringSafeArea(.all)
            }
    }
}

The sheet will still be draggable between sizes, but will not dismiss when you swipe down.

Conclusion

The new sheet abilities in SwiftUI 4 & iOS 16 look great and open up for new experiences, like onboarding guides, mandatory input modals etc. I can’t wait to use it in my apps.

With these great additions, there’s no need for me to keep working on BottomSheet. I will archive it and keep it around for a few years, then remove it.

Discussions & More

Please share any ideas, feedback or comments you may have in the Disqus section below, or by replying to this tweet

If you found this text interesting, make sure to follow me on Twitter and Mastodon for more content like this, and to be notified when new content is published.

If you like & want to support my work, please consider sponsoring me on GitHub Sponsors.