import UIKit /// 负责管理文本选区、UITextView 代理和菜单定位。 /// 对齐 WXRead 的选择模型:UITextView 处理原生选择,高亮通过 attributed string 属性绘制。 final class RDEPUBTextSelectionController: NSObject, UITextViewDelegate { private var isSelectionFromInteraction = false private var selectionMenuAnchorRect: CGRect? /// 选区变化回调,通知视图层更新 UI var onSelectionChanged: ((RDEPUBSelection?) -> Void)? /// 获取当前页面数据 var pageProvider: (() -> RDEPUBTextPage?)? // MARK: - UITextViewDelegate func textViewDidChangeSelection(_ textView: UITextView) { textViewDidChangeSelection(textView, page: pageProvider?()) } func textViewDidChangeSelection(_ textView: UITextView, page: RDEPUBTextPage?) { guard !isSelectionFromInteraction else { return } guard let page else { onSelectionChanged?(nil) return } let selectedRange = textView.selectedRange guard selectedRange.location != NSNotFound, selectedRange.length > 0, let attributedText = textView.attributedText else { onSelectionChanged?(nil) return } let source = attributedText.string as NSString let selectedText = source.substring(with: selectedRange).trimmingCharacters(in: .whitespacesAndNewlines) guard !selectedText.isEmpty else { onSelectionChanged?(nil) return } let globalStart = page.pageStartOffset + selectedRange.location let globalEnd = globalStart + selectedRange.length let totalLength = max(page.chapterContent.length - 1, 1) let selection = RDEPUBSelection( location: RDEPUBLocation( href: page.href, progression: Double(globalStart) / Double(totalLength), lastProgression: Double(max(globalEnd - 1, globalStart)) / Double(totalLength), fragment: nil ), text: selectedText, rangeInfo: RDEPUBTextOffsetRangeInfo(href: page.href, start: globalStart, end: globalEnd).jsonString() ) onSelectionChanged?(selection) } // MARK: - 选区清除 func clearSelection() { isSelectionFromInteraction = false selectionMenuAnchorRect = nil UIMenuController.shared.setMenuVisible(false, animated: true) onSelectionChanged?(nil) } func clearSelection(textView: UITextView) { textView.selectedRange = NSRange(location: 0, length: 0) clearSelection() } // MARK: - 菜单显示 func showSelectionMenuIfNeeded( in hostView: UIView, textView: UITextView, interactionController: RDEPUBPageInteractionController ) { guard textView.selectedRange.length > 0, let textRange = textView.selectedTextRange else { return } let rect = textView.firstRect(for: textRange) guard !rect.isNull, !rect.isEmpty else { return } let menuRect = hostView.convert(rect, from: textView) hostView.becomeFirstResponder() let menuController = UIMenuController.shared menuController.menuItems = [ UIMenuItem(title: "拷贝", action: #selector(RDEPUBTextContentView.rd_copy(_:))), UIMenuItem(title: "高亮", action: #selector(RDEPUBTextContentView.rd_highlight(_:))), UIMenuItem(title: "批注", action: #selector(RDEPUBTextContentView.rd_annotate(_:))) ] menuController.setTargetRect(menuRect, in: hostView) menuController.setMenuVisible(true, animated: true) } }