1

I am using the UISheetPresentationController in UIKit to present a UIViewController with the available default detents .medium and .large and with a custom smaller detent (with a height of around 100-120). This sheet will be presented on top of a UIViewController which shows a camera view/video live feed. The camera view is used to scan barcodes and only the visible area of the camera view should recognize the barcode (e.g. the camera feed below the bottom sheet should ignore any barcodes). Therefor I would need to get the currently selected height of the presented sheet to inset the recognition area of the camera view by that height. The selectedDetentIdentifier property of the UISheetPresentationController can be used in combination with the UISheetPresentationControllerDelegate to get the current detent and to respond to any changes of the sheet but the main issue is now that the Detent does not have a corresponding CGFloat value representing the real height of the sheet.

The only working solution that I was able to come up with was to use the resolvedValue (Documentation) to get the height of the detents. The issue with this approach is that for this function you need to have a UISheetPresentationControllerDetentResolutionContext which you can only access in the creation of a custom detent. So what I came up with is to resolve the values for each of the detents and store it in a property that can be used to inset the underlying camera view:

if let sheet = cameraViewController.sheetPresentationController {
    let mediumDetent = UISheetPresentationController.Detent.medium()
    let largeDetent = UISheetPresentationController.Detent.large()
    sheet.detents = [
        .custom(identifier: .small) { context in
            let small: CGFloat = 100
            self.sizes[UISheetPresentationController.Detent.Identifier.small] = small
            self.sizes[UISheetPresentationController.Detent.Identifier.medium] = mediumDetent.resolvedValue(in: context)
            self.sizes[UISheetPresentationController.Detent.Identifier.large] = largeDetent.resolvedValue(in: context)
            return small
        },
        mediumDetent,
        largeDetent
    ]
    sheet.largestUndimmedDetentIdentifier = .medium
    sheet.prefersGrabberVisible = true
    sheet.delegate = self

}
taskController.isModalInPresentation = true
present(taskController, animated: true, completion: completion)

I was then able to determine the height using the following code:

if let selectedDetentIdentifier = taskController.sheetPresentationController?.selectedDetentIdentifier,
   let height = sizes[selectedDetentIdentifier] {
    sheetHeight = height + view.safeAreaInsets.bottom // Sheet automatically adds the safeAreaInsets so we need to do that here as well.
}

But this seems more like a dirty workaround and nothing too reliable that should be used and therefor I would like to know if you have any other solutions or best practices?

1 Answers1

0

The approach you've taken to resolve the height of the custom detent using the resolvedValue function is a valid approach. However, as you've pointed out, it does have the limitation of requiring access to the UISheetPresentationControllerDetentResolutionContext which is only available during the creation of the detent.

Another approach to consider is to use Auto Layout to dynamically adjust the size of your camera view based on the size of the presented sheet. You can achieve this by creating constraints between the bottom of your camera view and the top of the presented sheet, and then updating the constant value of these constraints based on the height of the sheet.

To do this, you can create an outlet to the constraint that defines the distance between the bottom of your camera view and the top of the presented sheet. Then, in the delegate method didSelect of the UISheetPresentationControllerDelegate, you can update the constant value of this constraint based on the selected detent.

Here's an example of what the code might look like:

final class MyViewController: UIViewController {
    @IBOutlet weak var cameraViewBottomToSheetTopConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()

        if let sheet = cameraViewController.sheetPresentationController {
            let mediumDetent = UISheetPresentationController.Detent.medium()
            let largeDetent = UISheetPresentationController.Detent.large()
            sheet.detents = [
                .custom(identifier: .small) { context in
                    return 100
                },
                mediumDetent,
                largeDetent
            ]
            sheet.largestUndimmedDetentIdentifier = .medium
            sheet.prefersGrabberVisible = true
            sheet.delegate = self
        }

        taskController.isModalInPresentation = true
        present(taskController, animated: true, completion: completion)
    }
}

extension MyViewController: UISheetPresentationControllerDelegate {
    func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
        if let selectedDetentIdentifier = sheetPresentationController.selectedDetentIdentifier {
            switch selectedDetentIdentifier {
            case .medium:
                cameraViewBottomToSheetTopConstraint.constant = 300 // Update constraint constant for medium detent
            case .large:
                cameraViewBottomToSheetTopConstraint.constant = 500 // Update constraint constant for large detent
            case .small:
                cameraViewBottomToSheetTopConstraint.constant = 100 // Update constraint constant for custom small detent
            default:
                break
            }
        }
    }
}

This approach avoids the need to use the resolvedValue function and should provide a more reliable solution.

thedp
  • 8,350
  • 16
  • 53
  • 95
  • Thanks for your answer! I think the most crucial part is missing since you set the constraint value in the `sheetPresentationControllerDidChangeSelectedDetentIdentifier` to constant values (100, 300 or 500) depending on the selected detent. These values are currently fixed but need to be dynamic based on the actual sheet height (e.g. `.medium` could be 300 on an iPhone 8 and 450 on an iPhone 14 Pro). – Philipp Woessner May 03 '23 at 09:30