iOS PDFKit:如何直接检测PDFView的滚动事件?
iOS PDFKit:检测PDFView滚动与导航栏隐藏方案
好问题!我刚好在项目中处理过类似的需求,下面给你详细拆解可行的方案:
一、PDFView是否暴露底层UIScrollView?
PDFKit并没有把底层的UIScrollView作为公开属性直接暴露,但我们可以通过两种可靠的方式获取到它:
方法1:遍历子视图查找
PDFView的层级结构中,底层滚动视图是它的直接子视图之一,我们可以遍历子视图筛选出UIScrollView实例:
func getScrollView(from pdfView: PDFView) -> UIScrollView? { return pdfView.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView }
方法2:通过KVC获取(更简洁)
内部实现中,PDFView的滚动视图对应的键是scrollView,我们可以用KVC直接获取:
if let scrollView = pdfView.value(forKey: "scrollView") as? UIScrollView { // 拿到滚动视图 }
⚠️ 注意:虽然这不是苹果公开的API,但这个键值在PDFKit的多个版本中都保持稳定,实际开发中很少出现兼容性问题。如果担心未来变更,可以结合两种方法做降级处理。
二、检测滚动并控制导航栏的最优方案
获取到UIScrollView后,你就可以利用熟悉的UIScrollViewDelegate来处理滚动事件,完美替代你当前的临时手势方案:
完整代码示例
class PDFViewController: UIViewController, UIScrollViewDelegate { var pdfView: PDFView! var scrollView: UIScrollView? override func viewDidLoad() { super.viewDidLoad() // 初始化PDFView并添加到视图 pdfView = PDFView(frame: view.bounds) pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(pdfView) // 获取底层滚动视图并设置代理 scrollView = pdfView.value(forKey: "scrollView") as? UIScrollView scrollView?.delegate = self // 加载PDF文档(示例) if let url = Bundle.main.url(forResource: "sample", withExtension: "pdf") { pdfView.document = PDFDocument(url: url) } } // 开始拖动时隐藏导航栏 func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { navigationController?.setNavigationBarHidden(true, animated: true) } // 结束拖动(或减速停止)时显示导航栏 func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { showNavigationBar() } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { showNavigationBar() } private func showNavigationBar() { navigationController?.setNavigationBarHidden(false, animated: true) } }
替代方案:KVO监听documentVisibleRect
如果你不想依赖底层滚动视图,还可以通过KVO监听PDFView的documentVisibleRect属性——当用户滚动或缩放文档时,这个矩形区域会发生变化:
override func viewDidLoad() { super.viewDidLoad() // ... 初始化PDFView代码 ... // 添加KVO监听 pdfView.addObserver(self, forKeyPath: "documentVisibleRect", options: [.new], context: nil) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "documentVisibleRect", object is PDFView else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } // 这里可以判断是滚动还是缩放,比如对比前后的rect大小 // 如果是滚动则处理导航栏隐藏逻辑 navigationController?.setNavigationBarHidden(true, animated: true) // 可以添加延迟来自动显示导航栏 DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in self?.navigationController?.setNavigationBarHidden(false, animated: true) } } deinit { pdfView.removeObserver(self, forKeyPath: "documentVisibleRect") }
这种方案的好处是完全基于公开API,但需要额外处理缩放带来的回调(如果不需要区分滚动和缩放,可以忽略)。
三、关于你当前临时方案的问题
你提到的给父视图添加自定义平移手势并返回false的方式,虽然能触发导航栏隐藏,但可能会干扰PDFView本身的手势识别(比如双指缩放、长按选择文本等),而且无法精准区分滚动的开始和结束状态,所以还是建议使用上面的UIScrollViewDelegate方案,更符合系统原生的交互逻辑。
内容的提问来源于stack exchange,提问作者Michael Behan
相关产品推荐
相关产品推荐

