refactor: move highlight menu logic from Demo to SDK library
Add RDPDFReaderPageView as base page view class in SDK: - Handles all highlight menu interactions and display - Provides RDPDFReaderPageViewDelegate for menu action callbacks - Unifies with EPUB architecture: RDEPUBTextContentView pattern - Reduces code duplication for applications using the SDK Benefits: - Reusable menu interaction logic across all PDF apps - Consistent architecture with EPUB reader - Single responsibility principle (UI in library, persistence in app) - Backward compatible (existing code continues to work) Applications now implement RDPDFReaderPageViewDelegate to: - Respond to highlight menu actions - Handle data persistence - Refresh UI after changes Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
7c16647306
commit
5a19cfde14
@@ -0,0 +1,118 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
/// PDF 页面视图的基础类,提供统一的高亮菜单交互和文本选择菜单。
|
||||||
|
/// 与 EPUB 的 RDEPUBTextContentView 对应。
|
||||||
|
open class RDPDFReaderPageView: UIView, UIGestureRecognizerDelegate {
|
||||||
|
|
||||||
|
public weak var delegate: RDPDFReaderPageViewDelegate?
|
||||||
|
|
||||||
|
/// 宿主在页面视图创建时设置,用于显示真实内容(图片、PDF等)。
|
||||||
|
public let contentView = UIView()
|
||||||
|
|
||||||
|
/// 文本选择和高亮管理层,与 contentView 在同一坐标系。
|
||||||
|
public let textLayer = RDPDFReaderImageTextLayerView()
|
||||||
|
|
||||||
|
private var tappedHighlight: RDPDFReaderAnnotation?
|
||||||
|
|
||||||
|
public override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
setupViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
public required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
setupViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupViews() {
|
||||||
|
addSubview(contentView)
|
||||||
|
contentView.frame = bounds
|
||||||
|
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
|
||||||
|
textLayer.frame = contentView.bounds
|
||||||
|
textLayer.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
contentView.addSubview(textLayer)
|
||||||
|
|
||||||
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
|
||||||
|
addGestureRecognizer(tapGesture)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
|
||||||
|
let point = gesture.location(in: textLayer)
|
||||||
|
|
||||||
|
if let highlight = highlightAt(point) {
|
||||||
|
showExistingHighlightMenu(for: highlight, at: gesture.location(in: self))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func highlightAt(_ point: CGPoint) -> RDPDFReaderAnnotation? {
|
||||||
|
let currentPageAnnotations = textLayer.annotations.filter { $0.pageIndex == textLayer.pageIndex }
|
||||||
|
for annotation in currentPageAnnotations {
|
||||||
|
for rect in annotation.normalizedRects {
|
||||||
|
if rect.contains(point) {
|
||||||
|
return annotation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showExistingHighlightMenu(for annotation: RDPDFReaderAnnotation, at point: CGPoint) {
|
||||||
|
tappedHighlight = annotation
|
||||||
|
becomeFirstResponder()
|
||||||
|
|
||||||
|
var menuItems: [UIMenuItem] = [
|
||||||
|
UIMenuItem(title: "复制", action: #selector(rd_copy_highlight(_:)))
|
||||||
|
]
|
||||||
|
|
||||||
|
if annotation.note?.isEmpty == false {
|
||||||
|
menuItems.append(UIMenuItem(title: "删除注释", action: #selector(rd_delete_annotation(_:))))
|
||||||
|
} else {
|
||||||
|
menuItems.append(UIMenuItem(title: "注释", action: #selector(rd_annotate_highlight(_:))))
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.append(UIMenuItem(title: "删除划线", action: #selector(rd_delete_highlight(_:))))
|
||||||
|
|
||||||
|
let menuController = UIMenuController.shared
|
||||||
|
menuController.menuItems = menuItems
|
||||||
|
menuController.setTargetRect(CGRect(x: point.x, y: point.y, width: 1, height: 1), in: self)
|
||||||
|
menuController.setMenuVisible(true, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override open var canBecomeFirstResponder: Bool { true }
|
||||||
|
|
||||||
|
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||||
|
if tappedHighlight != nil {
|
||||||
|
return action == #selector(rd_copy_highlight(_:))
|
||||||
|
|| action == #selector(rd_delete_highlight(_:))
|
||||||
|
|| action == #selector(rd_annotate_highlight(_:))
|
||||||
|
|| action == #selector(rd_delete_annotation(_:))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func rd_copy_highlight(_ sender: Any?) {
|
||||||
|
guard let highlight = tappedHighlight else { return }
|
||||||
|
delegate?.pageView(self, didRequestHighlightMenuAction: .copy, highlight: highlight)
|
||||||
|
tappedHighlight = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func rd_annotate_highlight(_ sender: Any?) {
|
||||||
|
guard let highlight = tappedHighlight else { return }
|
||||||
|
delegate?.pageView(self, didRequestHighlightMenuAction: .annotate, highlight: highlight)
|
||||||
|
tappedHighlight = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func rd_delete_annotation(_ sender: Any?) {
|
||||||
|
guard let highlight = tappedHighlight else { return }
|
||||||
|
delegate?.pageView(self, didRequestHighlightMenuAction: .deleteAnnotation, highlight: highlight)
|
||||||
|
tappedHighlight = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func rd_delete_highlight(_ sender: Any?) {
|
||||||
|
guard let highlight = tappedHighlight else { return }
|
||||||
|
delegate?.pageView(self, didRequestHighlightMenuAction: .deleteUnderline, highlight: highlight)
|
||||||
|
tappedHighlight = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user