iosuiviewcontrolleruipresentationcontroller

Inset contents underneath sheet presented with medium detent


iOS 15 added UISheetPresentationController which allows you to implement a half-height sheet by setting sheetPresentationController's detents to [.medium()]. You can then set the largestUndimmedDetentIdentifier to .medium to allow interacting with the presenting view controller and the sheet so you can achieve an interface similar to Maps and Stocks.

My question is, how could you detect when an undimmed sheet is presented and get its height in order to inset content in the underlying screen(s). For example, I want to add content insets to my table view so that you can scroll to see all cells. Currently, the sheet covers up the bottom cells making it impossible to interact with those - you can only tap the ones that lie above where the sheet rests.


Solution

  • To do this, set the sheetPresentationController.delegate and adopt the UISheetPresentationControllerDelegate protocol. This will inform you when it will present and did dismiss. You can then animate moving your content as appropriate in an animateChanges block. Here's an example:

    extension ViewController: UISheetPresentationControllerDelegate {
    
        func presentationController(_ presentationController: UIPresentationController, willPresentWithAdaptiveStyle style: UIModalPresentationStyle, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
            updateContentsForPresentedSheet(isPresented: true)
            (presentationController as? UISheetPresentationController)?.animateChanges {
                view.layoutIfNeeded()
            }
        }
        
        func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
            updateContentsForPresentedSheet(isPresented: false)
            (presentationController as? UISheetPresentationController)?.animateChanges {
                view.layoutIfNeeded()
            }
        }
    }
    
    private func updateContentsForPresentedSheet(isPresented: Bool) {
        bottomConstraint.constant = isPresented ? view.bounds.height / 2 : 0
    }
    

    Note the medium detent height is seemingly indeterminate but close to half the screen height. If you need to precisely adjust your interface, iOS 16 adds the ability to provide a custom detent where you specify the exact height desired. Here's an example:

    viewController.sheetPresentationController?.detents = [.custom(resolver: { [weak self] context in
        guard let self else { return nil }
        // Note the system automatically accommodates the safe area so we'll subtract those insets
        return self.view.bounds.height / 2 - self.view.safeAreaInsets.bottom
    })]