如何实现自定义PDFView中TextView随对应PDF页面滚动固定?
解决PDFView中编辑态TextView随页面滚动的方案
方案一:监听滚动实时更新TextView位置
直接给PDFView添加的子视图不会自动关联PDF页面,需要通过坐标转换+滚动监听来同步位置:
- 记录点击时的页面关联信息
点击PDFView时,获取点击位置对应的PDFPage,并将屏幕坐标转换为该页面内部的相对坐标(PDFKit以页面左下角为原点),把这些信息和TextView绑定(用关联对象或自定义属性存储)。 - 监听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的注释视图原生随页面滚动的特性,创建自定义注释及对应的编辑视图:
- 自定义PDFAnnotation
创建继承自PDFAnnotation的类,用于存储编辑的文本内容。 - 自定义PDFAnnotationView
实现包含TextView的注释视图,TextView直接作为注释视图的子视图,编辑时的文本同步到注释对象中。 - 绑定点击事件与注释显示
点击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
相关产品推荐
相关产品推荐

