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

iOS Swift中如何在PDFKit同时实现Apple Pencil批注与手指触控导航

解决方案:同时支持Apple Pencil批注与PDF导航(无需切换交互状态)

我之前开发PDF批注应用时也碰到过这个痛点——频繁切换isUserInteractionEnabled确实影响体验。下面给你两种可行的方案,都能让你保留touchesBegan/Moved/Ended的批注逻辑,同时用手指正常操作PDF的平移和缩放:

方案一:自定义手势识别器+触控类型过滤

这种方法的核心是:保持PDFView的交互开启,通过自定义手势识别器处理手指的导航操作,同时过滤掉Apple Pencil的触控,让Pencil事件能传递到ViewController的touches方法里。

步骤1:添加自定义手势并禁用PDFView自带手势

在你的ViewController里添加平移和捏合手势,同时关闭PDFView自带的同类手势避免冲突:

import UIKit
import PDFKit

class ViewController: UIViewController {
    var pdfView: PDFView!
    private var panGesture: UIPanGestureRecognizer!
    private var pinchGesture: UIPinchGestureRecognizer!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        pdfView = PDFView()
        pdfView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(pdfView)
        
        // 布局约束
        NSLayoutConstraint.activate([
            pdfView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            pdfView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            pdfView.topAnchor.constraint(equalTo: view.topAnchor),
            pdfView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        // 加载PDF
        let url = Bundle.main.url(forResource: "TestPDF", withExtension: "pdf")!
        pdfView.document = PDFDocument(url: url)
        pdfView.isUserInteractionEnabled = true // 保持交互开启
        
        // 添加自定义平移手势
        panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        panGesture.delegate = self
        pdfView.addGestureRecognizer(panGesture)
        
        // 添加自定义捏合手势
        pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))
        pinchGesture.delegate = self
        pdfView.addGestureRecognizer(pinchGesture)
        
        // 禁用PDFView自带的平移/捏合手势,避免冲突
        pdfView.gestureRecognizers?.forEach {
            if $0 is UIPanGestureRecognizer || $0 is UIPinchGestureRecognizer {
                $0.isEnabled = false
            }
        }
    }

步骤2:实现手势处理逻辑

模拟PDFView原生的平移和缩放行为:

@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
        guard let scrollView = pdfView.documentView as? UIScrollView else { return }
        let translation = gesture.translation(in: scrollView)
        // 调整scrollView的内容偏移
        scrollView.contentOffset = CGPoint(
            x: scrollView.contentOffset.x - translation.x,
            y: scrollView.contentOffset.y - translation.y
        )
        gesture.setTranslation(.zero, in: scrollView)
    }

    @objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) {
        guard gesture.state == .changed else { return }
        // 调整PDF缩放比例
        pdfView.scaleFactor *= gesture.scale
        gesture.scale = 1.0
    }

步骤3:过滤Pencil触控

通过手势代理,只让手指触发导航手势,Pencil事件直接传递给ViewController的touches方法:

extension ViewController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        // 只有非Pencil的触控才让手势响应
        return touch.type != .stylus
    }
}

保留你的批注逻辑

原来的touches方法可以完全保留,现在Pencil触控会正常触发这些方法:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touchesBegan called!")
        if let touch = touches.first, touch.type == .stylus {
            print("touchesBegan pencil annotation ...")
            // 这里添加你的批注绘制逻辑
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touchesMoved called!")
        if let touch = touches.first, touch.type == .stylus {
            print("touchesMoved pencil annotation ...")
            // 这里添加你的批注绘制逻辑
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touchesEnded called!")
        if let touch = touches.first, touch.type == .stylus {
            print("touchesEnded pencil annotation ...")
            // 这里添加你的批注绘制逻辑
        }
    }
}

方案二:自定义PDFView子类(更推荐)

这种方法更优雅,直接在PDFView的事件传递环节区分触控类型,Pencil事件转发给ViewController处理批注,手指事件交给PDFView原生处理导航,不用自己实现平移缩放逻辑。

步骤1:自定义PDFView并添加代理

创建一个继承自PDFView的子类,通过代理将Pencil事件转发给ViewController:

import UIKit
import PDFKit

// 定义代理协议,用于传递Pencil触控事件
protocol PDFAnnotationDelegate: AnyObject {
    func handleStylusTouchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
    func handleStylusTouchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
    func handleStylusTouchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
}

class CustomPDFView: PDFView {
    weak var annotationDelegate: PDFAnnotationDelegate?

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first, touch.type == .stylus {
            // Pencil事件转发给代理(ViewController)
            annotationDelegate?.handleStylusTouchesBegan(touches, with: event)
        } else {
            // 手指事件交给父类处理(原生导航)
            super.touchesBegan(touches, with: event)
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first, touch.type == .stylus {
            annotationDelegate?.handleStylusTouchesMoved(touches, with: event)
        } else {
            super.touchesMoved(touches, with: event)
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first, touch.type == .stylus {
            annotationDelegate?.handleStylusTouchesEnded(touches, with: event)
        } else {
            super.touchesEnded(touches, with: event)
        }
    }
}

步骤2:在ViewController中使用CustomPDFView

替换原来的PDFView为自定义子类,实现代理方法处理批注:

import UIKit
import PDFKit

class ViewController: UIViewController, PDFAnnotationDelegate {
    var pdfView: CustomPDFView!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        pdfView = CustomPDFView()
        pdfView.translatesAutoresizingMaskIntoConstraints = false
        pdfView.annotationDelegate = self // 设置代理
        view.addSubview(pdfView)
        
        // 布局约束
        NSLayoutConstraint.activate([
            pdfView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            pdfView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            pdfView.topAnchor.constraint(equalTo: view.topAnchor),
            pdfView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        // 加载PDF
        let url = Bundle.main.url(forResource: "TestPDF", withExtension: "pdf")!
        pdfView.document = PDFDocument(url: url)
        pdfView.isUserInteractionEnabled = true // 保持交互开启
    }

    // 实现代理方法,处理Pencil批注
    func handleStylusTouchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touchesBegan called!")
        if let touch = touches.first {
            print("touchesBegan pencil annotation ...")
            // 这里添加你的批注绘制逻辑
        }
    }

    func handleStylusTouchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touchesMoved called!")
        if let touch = touches.first {
            print("touchesMoved pencil annotation ...")
            // 这里添加你的批注绘制逻辑
        }
    }

    func handleStylusTouchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touchesEnded called!")
        if let touch = touches.first {
            print("touchesEnded pencil annotation ...")
            // 这里添加你的批注绘制逻辑
        }
    }
}

为什么这两种方案可行?

原来的问题在于:当PDFView.isUserInteractionEnabled = true时,它内部的UIScrollView会优先拦截所有触控事件,导致ViewController的touches方法无法触发。这两种方案都是通过在事件传递链的早期区分触控类型,让Pencil事件绕过PDFView的拦截,直接到达你的批注逻辑,同时保留手指对PDF的原生导航操作。

个人更推荐方案二,因为它不需要自己实现平移缩放的细节,完全复用PDFView的原生行为,代码更简洁也更稳定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:35:51