- A presentation controller that manages the appearance and behavior of a sheet.
- Sheets are a subclass of
UISheetPresentationController
- a height where a sheet naturally rests
- a fraction of fully defined sheet frame (medium 1/2, large full)
To make a child view controller appear has a 1/2 sized sheet:
@objc func presentModal(sender: UIButton) {
// Create your child view controller
let detailViewController = DetailViewController()
// Set its sheet properties
if let sheet = detailViewController.sheetPresentationController {
sheet.detents = [.medium(), .large()]
}
present(detailViewController, animated: true, completion: nil)
}
By default this sheet will:
- expand when swiped up
- expand when scrolled up
You can turn off the sheet expansion due to a scroll like this:
@objc func presentModal(sender: UIButton) {
let detailViewController = DetailViewController()
let nav = UINavigationController(rootViewController: detailViewController)
nav.modalPresentationStyle = .pageSheet
if let sheet = nav.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersScrollingExpandsWhenScrolledToEdge = false // add
}
present(nav, animated: true, completion: nil)
}
You can still make the sheet a large()
detent height. You just need to grab the navigation bar and drag it up.
Note here we are embedded in a navigation controller.
You can start in full detent mode like this:
sheet.detents = [.medium(), .large()]
sheet.selectedDetentIdentifier = .large
Also be sure to specifiy these as smallest to largest detents.
By default, when you enable a bottom sheet, UIKit disables the underneath content by dimming it and making it non-interactable.
You can change this behavior be setting the largestUndimmedDetentIdentifier
on the medium
detent.
That will make the dimming go away on the medium
detent and allow you to interact with the view underneath.
@objc func presentModal(sender: UIButton) {
let detailViewController = DetailViewController()
let nav = UINavigationController(rootViewController: detailViewController)
nav.modalPresentationStyle = .pageSheet
if let sheet = nav.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.largestUndimmedDetentIdentifier = .medium // add
}
present(nav, animated: true, completion: nil)
}
To prevent the sheet from every dismissing set isModalInPresentation
to true
.
@objc func presentModal(sender: UIButton) {
let detailViewController = DetailViewController()
let nav = UINavigationController(rootViewController: detailViewController)
nav.modalPresentationStyle = .pageSheet
nav.isModalInPresentation = true // add
if let sheet = nav.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.largestUndimmedDetentIdentifier = .medium
}
present(nav, animated: true, completion: nil)
}
Now if you want to dismiss the sheet, you'll need to add buttons or something else to the nav
bar.
You can change the size of the sheets programmatically by setting selectedDetentIdentifier
.
let detailViewController = DetailViewController()
let nav = UINavigationController(rootViewController: detailViewController)
if let sheet = nav.sheetPresentationController {
sheet.detents = [.medium(), .large()]
}
let medium = UIBarButtonItem(title: "Medium", primaryAction: .init(handler: { _ in
if let sheet = nav.sheetPresentationController {
sheet.animateChanges {
sheet.selectedDetentIdentifier = .medium
}
}
}))
let large = UIBarButtonItem(title: "Large", image: nil, primaryAction: .init(handler: { _ in
if let sheet = nav.sheetPresentationController {
sheet.animateChanges {
sheet.selectedDetentIdentifier = .large
}
}
}))
detailViewController.navigationItem.leftBarButtonItem = medium
detailViewController.navigationItem.rightBarButtonItem = large
present(nav, animated: true, completion: nil)
sheet.preferredCornerRadius = 50
A grabber is a visual clue that a sheet is resizable.
sheet.prefersGrabberVisible = true