Skip to content

Presenting and Customizing Sheets

Sheets in UIKit, introduced in iOS 15, provide a modern, flexible way to present modal content that slides up from the bottom of the screen, often used for forms, options, or contextual actions. Sheets are implemented using UISheetPresentationController, which offers customizable presentation styles and behaviors. This document covers presenting sheets and customizing their appearance and behavior in UIKit.

Presenting Sheets

Sheets are presented using a UIViewController configured with a UISheetPresentationController. This controller manages the sheet’s presentation, allowing it to appear as a partial or full-screen modal view.

Key Components

  • UISheetPresentationController: Manages the sheet’s presentation, size, and interaction.
  • UIViewController: The view controller presented as a sheet.
  • Detents: Define the sheet’s height (e.g., .medium, .large, or custom).
  • Presentation Style: Set to .formSheet or .pageSheet for sheet behavior.

Basic Example

Present a view controller as a sheet with medium and large detents:

swift
import UIKit

class ViewController: UIViewController {
    @IBAction func presentSheet(_ sender: UIButton) {
        let sheetViewController = SheetViewController()
        sheetViewController.modalPresentationStyle = .pageSheet
        
        if let sheet = sheetViewController.sheetPresentationController {
            sheet.detents = [.medium(), .large()]
            sheet.prefersGrabberVisible = true
            sheet.prefersScrollingExpandsWhenScrolledToEdge = false
        }
        
        present(sheetViewController, animated: true, completion: nil)
    }
}

class SheetViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        let label = UILabel()
        label.text = "Sheet Content"
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

Key Points

  • Modal Presentation Style: Set modalPresentationStyle to .pageSheet (default for sheets) or .formSheet for a narrower appearance on iPad.
  • Detents: Use .medium() (half-screen) and .large() (near full-screen) to define sheet sizes. iOS 16+ supports custom detents.
  • Grabber: Set prefersGrabberVisible to true to show a drag indicator at the top.
  • Presentation: Use present(_:animated:completion:) to display the sheet.

Customizing Sheets

UISheetPresentationController offers extensive customization options, including detents, edge behavior, and appearance.

Custom Detents (iOS 16+)

Define custom sheet heights using UISheetPresentationController.Detent.custom(identifier:resolver:).

swift
if let sheet = sheetViewController.sheetPresentationController {
    let customDetent = UISheetPresentationController.Detent.custom(identifier: .init("custom")) { context in
        return context.maximumDetentValue * 0.3 // 30% of max height
    }
    sheet.detents = [customDetent, .medium(), .large()]
    sheet.prefersGrabberVisible = true
}

Selected Detent

Set the initial detent using selectedDetentIdentifier:

swift
sheet.selectedDetentIdentifier = .medium

Edge Behavior

Control how the sheet interacts with edges:

swift
sheet.prefersEdgeAttachedInCompactHeight = true // Attach to bottom in compact height
sheet.prefersScrollingExpandsWhenScrolledToEdge = false // Prevent scrolling from expanding detent
sheet.largestUndimmedDetentIdentifier = .medium // Allow interaction with background at medium detent

Corner Radius and Width

Customize appearance:

swift
sheet.preferredCornerRadius = 20 // Round corners
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true // Respect preferredContentSize

Dismissing Interactively

Sheets are dismissed by dragging down (if prefersGrabberVisible is true) or programmatically:

swift
dismiss(animated: true, completion: nil)

Example with Customization

swift
@IBAction func presentCustomSheet(_ sender: UIButton) {
    let sheetViewController = SheetViewController()
    sheetViewController.modalPresentationStyle = .pageSheet
    
    if let sheet = sheetViewController.sheetPresentationController {
        // Custom detent (iOS 16+)
        let customDetent = UISheetPresentationController.Detent.custom(identifier: .init("small")) { context in
            return context.maximumDetentValue * 0.25
        }
        sheet.detents = [customDetent, .medium(), .large()]
        sheet.selectedDetentIdentifier = .medium
        sheet.prefersGrabberVisible = true
        sheet.prefersEdgeAttachedInCompactHeight = true
        sheet.largestUndimmedDetentIdentifier = .medium
        sheet.preferredCornerRadius = 15
    }
    
    present(sheetViewController, animated: true, completion: nil)
}

Handling Detent Changes

Observe detent changes using the delegate:

swift
class ViewController: UIViewController, UISheetPresentationControllerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Set delegate
        sheetViewController.sheetPresentationController?.delegate = self
    }
    
    func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
        if let detent = sheetPresentationController.selectedDetentIdentifier {
            print("Selected detent: \(detent)")
        }
    }
}

iPad Considerations

On iPad, sheets behave like .formSheet or .pageSheet but may require additional configuration:

swift
sheetViewController.preferredContentSize = CGSize(width: 400, height: 600) // Set size for iPad

Best Practices

  • Detent Selection: Offer at least two detents (e.g., .medium, .large) for flexibility.
  • Grabber: Enable prefersGrabberVisible for discoverability of drag-to-dismiss.
  • Background Interaction: Use largestUndimmedDetentIdentifier to allow background interaction when appropriate.
  • Animation: Ensure smooth transitions by using animated: true in present and dismiss.
  • Accessibility: Ensure content in the sheet is VoiceOver-compatible and legible at all detent sizes.
  • Testing: Test on iPhone and iPad, in various orientations and multitasking modes.
  • Minimal Customization: Avoid excessive visual changes to maintain system consistency.

Limitations

  • iOS 15+: Sheets via UISheetPresentationController are available only on iOS 15 and later.
  • Custom Detents: Available only on iOS 16 and later.
  • iPad Behavior: Sheets may appear as floating windows on iPad; test thoroughly.
  • Content Size: Ensure content adapts to dynamic detent heights to avoid clipping.

Summary

Sheets in UIKit, managed by UISheetPresentationController, provide a modern, flexible way to present modal content. With customizable detents, edge behavior, and appearance, developers can create intuitive and responsive sheet presentations. By following best practices, sheets can enhance the user experience in UIKit-based iOS apps while maintaining system consistency.

Released under the MIT License.