如何获取UISheetPresentationController当前Detent的高度?
现有临时方案(存在可靠性问题)
你当前的思路是利用自定义detent闭包中可访问UISheetPresentationControllerDetentResolutionContext的特性,在创建自定义detent时提前解析并存储所有detent的高度:
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[.small] = small self.sizes[.medium] = mediumDetent.resolvedValue(in: context) self.sizes[.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)
之后通过存储的字典获取高度,并补充底部安全区Insets:
if let selectedDetentIdentifier = taskController.sheetPresentationController?.selectedDetentIdentifier, let height = sizes[selectedDetentIdentifier] { sheetHeight = height + view.safeAreaInsets.bottom }
但该方案依赖自定义detent的初始化时机,若后续sheet的detent配置被修改,存储的高度会失效,属于临时 workaround。
更可靠的解决方案与最佳实践
方案1:直接监听Sheet视图的Frame变化(推荐)
Sheet的高度变化会直接反映在其view的frame属性上,通过代理或KVO可以实时获取准确的渲染高度:
方式A:利用代理监听Detent切换
在UISheetPresentationControllerDelegate的回调中直接获取Sheet视图高度:
func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) { // 获取Sheet的实际渲染高度(已包含底部安全区) let sheetActualHeight = sheetPresentationController.view.frame.height // 调整扫码区域的底部内边距 updateScanRegionBottomInset(sheetActualHeight) }
方式B:KVO监听实时布局变化(支持拖拽过程中更新)
如果需要在用户拖拽Sheet的过程中实时调整扫码区域,可以给Sheet的视图添加frame属性的KVO监听:
// 初始化Sheet时添加监听 if let sheetView = sheetPresentationController?.view { sheetView.addObserver(self, forKeyPath: #keyPath(UIView.frame), options: [.new], context: nil) } // KVO回调实现 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == #keyPath(UIView.frame), let sheetView = object as? UIView else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } let sheetActualHeight = sheetView.frame.height updateScanRegionBottomInset(sheetActualHeight) } // 记得在deinit中移除监听 deinit { sheetPresentationController?.view.removeObserver(self, forKeyPath: #keyPath(UIView.frame)) }
方案2:封装Detent高度实时解析方法
如果需要提前获取某个Detent的预期高度(比如预计算扫码区域),可以封装一个扩展方法,实时利用Sheet的上下文解析Detent高度:
extension UISheetPresentationController { /// 实时解析指定Detent的高度(不含底部安全区) func resolvedHeight(for detentId: Detent.Identifier) -> CGFloat? { guard let targetDetent = detents.first(where: { $0.identifier == detentId }) else { return nil } // 利用当前Sheet的视图上下文构建解析环境 let resolutionContext = DetentResolutionContext( containerView: view, traitCollection: traitCollection, safeAreaInsets: view.safeAreaInsets ) return targetDetent.resolvedValue(in: resolutionContext) } }
使用时直接调用:
if let selectedDetentId = sheetPresentationController?.selectedDetentIdentifier, let detentHeight = sheetPresentationController?.resolvedHeight(for: selectedDetentId) { // 加上底部安全区得到实际高度 let totalSheetHeight = detentHeight + view.safeAreaInsets.bottom updateScanRegionBottomInset(totalSheetHeight) }
最佳实践总结
- 优先使用Sheet视图Frame监听:直接获取实际渲染高度,不受Detent配置变化影响,是最准确可靠的方式。
- 避免提前存储高度:设备旋转、安全区变化、Sheet配置修改都会导致预存高度失效,尽量实时获取。
- 按需选择监听粒度:仅需Detent切换时的高度用代理即可,需要拖拽过程中实时更新则用KVO。
内容的提问来源于stack exchange,提问作者Philipp Woessner
相关产品推荐
相关产品推荐

