You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取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)
}

最佳实践总结

  1. 优先使用Sheet视图Frame监听:直接获取实际渲染高度,不受Detent配置变化影响,是最准确可靠的方式。
  2. 避免提前存储高度:设备旋转、安全区变化、Sheet配置修改都会导致预存高度失效,尽量实时获取。
  3. 按需选择监听粒度:仅需Detent切换时的高度用代理即可,需要拖拽过程中实时更新则用KVO。

内容的提问来源于stack exchange,提问作者Philipp Woessner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 09:02:49