Presenting a bottom sheet in UIKit with UISheetPresentationController

Published on: June 30, 2021

We've seen bottom sheets in Apple's apps for a while now, and plenty of apps have followed this pattern. If you're not sure what I mean, it's the kind of sheet that takes up just a part of the screen and can be swiped upwards to take up the whole screen or downwards to be dismissed. Here's an example from Apple's Maps app:

To implement a sheet like this, we used to require third party tools, or we needed to get creative and implement this pattern ourselves.

With iOS 15, Apple introduced UISheetPresentationController which allows us to implement bottom sheets with just a few lines of code.

When you present a view controller as shown below, you know that your presented view controller will be shown as a "card" on top of the presenting view controller:

present(targetViewController, animated: true)

By default, the user can already swipe the presented view controller down to dismiss it interactively.

A view controller that is presented with present(_:animated:) on iOS 15 will have a UISheetPresentationController set as its presentation controller automatically. This presentation controller is responsible for managing the transition between the presented and the presenting view controller, and it handles interaction like interactive dismissal.

It also handles the bottom sheet behavior that we want to implement. So that our view controller will first take up half the screen, and then can be expanded to full height and back down again.

To implement this, you use so called detents. These detents are set on the UISheetPresentationController and will be used to determine how the view controller can be shown. We can choose between medium, large, or both. Using only a medium detent will make your presented view controller take up roughly half the height of the screen. A large detent is the default and it makes the presented view controller its full height. Using both will allow the user to swipe between medium and large.

Here's how you set the UISheetPresentationController's detents:

override func viewDidLoad() {
    super.viewDidLoad()

    if let sheetController = self.presentationController as? UISheetPresentationController {
        sheetController.detents = [.medium(), .large()]
    }
}

You simply check if the view controller's presentation controller is an instance of UISheetPresentationController, and if it is you assign its detents.

Apple's Maps implementation shows a nice grabber that indicates to users that they can drag the bottom sheet up or down. This grabber isn't shown by default so you'll need to enable it manually if you want it to be shown:

sheetController.prefersGrabberVisible = true

This new feature is very nice, and I love how easy Apple has made it to implement a bottom sheet in iOS 15.

Unfortunately, we can't (easily) use this feature in SwiftUI. But if you're interested in a workaround... I have one for you right here.

Categories

Swift