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

如何实现自定义PDFView中TextView随对应PDF页面滚动固定?

解决PDFView中编辑态TextView随页面滚动的方案

方案一:监听滚动实时更新TextView位置

直接给PDFView添加的子视图不会自动关联PDF页面,需要通过坐标转换+滚动监听来同步位置:

  1. 记录点击时的页面关联信息
    点击PDFView时,获取点击位置对应的PDFPage,并将屏幕坐标转换为该页面内部的相对坐标(PDFKit以页面左下角为原点),把这些信息和TextView绑定(用关联对象或自定义属性存储)。
  2. 监听PDFView的滚动事件
    获取PDFView内部的scrollView并设置代理,在scrollViewDidScroll方法中,将之前保存的页面相对坐标重新转换为当前屏幕坐标,实时更新TextView的frame。

代码示例:

// 定义关联对象Key,用于存储TextView和PDF页面的关联信息
private struct AssociatedKeys {
    static var pdfPage = "pdfPage"
    static var pageRelativePoint = "pageRelativePoint"
}

// 处理点击事件,创建TextView并绑定页面信息
func handleTap(_ gesture: UITapGestureRecognizer) {
    let tapScreenPoint = gesture.location(in: pdfView)
    guard let targetPage = pdfView.page(for: tapScreenPoint, nearest: true),
          let pageBounds = targetPage.bounds(for: pdfView.displayBox) else { return }
    
    // 转换为PDF页面内部的相对坐标
    let pageRelativePoint = pdfView.convert(tapScreenPoint, to: targetPage)
    
    // 创建TextView
    let textView = UITextView(frame: CGRect(x: tapScreenPoint.x, y: tapScreenPoint.y, width: 200, height: 100))
    textView.backgroundColor = .yellow.withAlphaComponent(0.5)
    pdfView.addSubview(textView)
    
    // 绑定页面和相对坐标到TextView
    objc_setAssociatedObject(textView, &AssociatedKeys.pdfPage, targetPage, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    objc_setAssociatedObject(textView, &AssociatedKeys.pageRelativePoint, NSValue(cgPoint: pageRelativePoint), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}

// 监听滚动更新位置
extension CustomPDFView: UIScrollViewDelegate {
    override func awakeFromNib() {
        super.awakeFromNib()
        pdfView.scrollView.delegate = self
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard let textView = pdfView.subviews.first(where: { $0 is UITextView }) as? UITextView,
              let targetPage = objc_getAssociatedObject(textView, &AssociatedKeys.pdfPage) as? PDFPage,
              let pointValue = objc_getAssociatedObject(textView, &AssociatedKeys.pageRelativePoint) as? NSValue else { return }
        
        let pageRelativePoint = pointValue.cgPointValue
        // 将页面相对坐标转换为当前屏幕坐标
        let currentScreenPoint = pdfView.convert(pageRelativePoint, from: targetPage)
        // 更新TextView位置(保持宽高不变)
        var textViewFrame = textView.frame
        textViewFrame.origin = currentScreenPoint
        textView.frame = textViewFrame
    }
}

方案二:基于PDFAnnotation实现原生滚动

利用PDFKit的注释视图原生随页面滚动的特性,创建自定义注释及对应的编辑视图:

  1. 自定义PDFAnnotation
    创建继承自PDFAnnotation的类,用于存储编辑的文本内容。
  2. 自定义PDFAnnotationView
    实现包含TextView的注释视图,TextView直接作为注释视图的子视图,编辑时的文本同步到注释对象中。
  3. 绑定点击事件与注释显示
    点击PDFView时创建自定义注释并添加到对应页面,通过PDFViewDelegate的方法返回自定义注释视图,TextView会随注释自动跟随页面滚动。

代码示例:

// 自定义编辑注释类
class EditableTextAnnotation: PDFAnnotation {
    var editText: String = ""
    
    override init(bounds: CGRect, forType annotationType: PDFAnnotationType, withProperties properties: [AnyHashable : Any]?) {
        super.init(bounds: bounds, forType: .freeText, withProperties: properties)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// 自定义注释视图(包含TextView)
class EditableTextAnnotationView: PDFAnnotationView {
    let editTextView = UITextView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupTextView()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupTextView() {
        editTextView.frame = bounds
        editTextView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        editTextView.backgroundColor = .yellow.withAlphaComponent(0.5)
        addSubview(editTextView)
        // 同步文本到注释对象
        editTextView.addTarget(self, action: #selector(textDidChange), for: .editingChanged)
    }
    
    @objc private func textDidChange() {
        guard let editableAnnotation = annotation as? EditableTextAnnotation else { return }
        editableAnnotation.editText = editTextView.text
    }
    
    override var annotation: PDFAnnotation? {
        didSet {
            guard let editableAnnotation = annotation as? EditableTextAnnotation else { return }
            editTextView.text = editableAnnotation.editText
        }
    }
}

// 在CustomPDFView中处理点击和代理逻辑
extension CustomPDFView: PDFViewDelegate {
    func handleTap(_ gesture: UITapGestureRecognizer) {
        let tapScreenPoint = gesture.location(in: pdfView)
        guard let targetPage = pdfView.page(for: tapScreenPoint, nearest: true),
              let pageBounds = targetPage.bounds(for: pdfView.displayBox) else { return }
        
        // 转换为PDF页面坐标(注意PDF原点在左下角,调整Y轴)
        let pagePoint = pdfView.convert(tapScreenPoint, to: targetPage)
        let annotationBounds = CGRect(x: pagePoint.x, y: pageBounds.height - pagePoint.y - 100, width: 200, height: 100)
        
        // 创建并添加自定义注释
        let editableAnnotation = EditableTextAnnotation(bounds: annotationBounds, forType: .freeText, withProperties: nil)
        targetPage.addAnnotation(editableAnnotation)
        
        // 触发注释视图显示
        pdfView.select(nil)
        pdfView.select(editableAnnotation)
    }
    
    func pdfView(_ pdfView: PDFView, viewFor annotation: PDFAnnotation) -> PDFAnnotationView? {
        guard annotation is EditableTextAnnotation else { return nil }
        return EditableTextAnnotationView(frame: annotation.bounds)
    }
}

方案对比

  • 方案一:适合临时编辑场景,不需要修改原PDF文件,实现简单但需要手动维护坐标同步。
  • 方案二:贴合PDFKit原生机制,编辑完成后可直接将注释保存到PDF实现持久化,更适合需要保存编辑内容的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 10:57:41