diff --git a/Sources/RDPDFReaderView/Sources/RDPDFReaderPageView.swift b/Sources/RDPDFReaderView/Sources/RDPDFReaderPageView.swift new file mode 100644 index 0000000..a3baba0 --- /dev/null +++ b/Sources/RDPDFReaderView/Sources/RDPDFReaderPageView.swift @@ -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 + } +}