- 新增 RDEPUBReaderSearchCoordinator 与 RDEPUBSelectionState 管理搜索和选中状态 - 新增 BookmarkChromeStateTests、NavigationBackwardTests、SelectionAnnotateTests 等 UI 测试 - 新增多个边界测试 epub 样本(损坏结构、空归档、缺失文件、流式外链验证) - 重构阅读器 chrome 状态管理,统一 tool bar 与 search bar 交互 - 优化大书分页缓存策略(RDEPUBChapterSummaryDiskCache、RDEPUBPageCountCache) - 移除废弃的 RDEPUBLocationConverter 和 RDEPUBPageBreakPolicy - 更新 epub-bridge.js 与 JS bridge 通信协议 - 全面更新现有 UI 测试以适配新的 helper 和状态管理
119 lines
5.0 KiB
Swift
119 lines
5.0 KiB
Swift
// RDEPUBWebView+Configuration.swift
|
||
// RDEPUBWebView 的 WKWebView 配置扩展
|
||
// 负责按需创建和配置 WKWebView 实例:注册 JS 桥接消息处理器、
|
||
// 注入用户脚本、设置自定义 URL Scheme 处理器、配置滚动和外观属性。
|
||
// 同时处理选择菜单(拷贝/高亮/批注)和 WebView 销毁清理。
|
||
|
||
import UIKit
|
||
import WebKit
|
||
|
||
extension RDEPUBWebView {
|
||
/// 按需配置 WKWebView(仅在 Publication 变更或 WebView 不存在时重建)
|
||
func configureWebViewIfNeeded(publication: RDEPUBPublication) {
|
||
let parser = publication.parser
|
||
let publicationKey = parser.opfURL?.path ?? parser.extractionRootURL?.path ?? UUID().uuidString
|
||
if publicationKey == configuredPublicationKey, webView != nil {
|
||
return
|
||
}
|
||
|
||
teardownWebView()
|
||
|
||
let userContentController = WKUserContentController()
|
||
RDEPUBJavaScriptBridge.messageNames.forEach {
|
||
userContentController.add(self, name: $0)
|
||
}
|
||
userContentController.addUserScript(
|
||
WKUserScript(
|
||
source: RDEPUBJavaScriptBridge.userScript,
|
||
injectionTime: .atDocumentEnd,
|
||
forMainFrameOnly: true
|
||
)
|
||
)
|
||
|
||
let configuration = WKWebViewConfiguration()
|
||
configuration.websiteDataStore = .nonPersistent()
|
||
configuration.userContentController = userContentController
|
||
let schemeHandler = RDEPUBResourceURLSchemeHandler(parser: parser)
|
||
configuration.setURLSchemeHandler(schemeHandler, forURLScheme: RDEPUBResourceURLSchemeHandler.scheme)
|
||
|
||
let webView = RDEPUBAnnotationWebView(frame: bounds, configuration: configuration)
|
||
webView.onSelectionAction = { [weak self] action in
|
||
guard let self else { return }
|
||
self.handleSelectionMenuAction(action)
|
||
}
|
||
UIMenuController.shared.menuItems = [
|
||
UIMenuItem(title: "拷贝", action: #selector(RDEPUBAnnotationWebView.rd_copy(_:))),
|
||
UIMenuItem(title: "高亮", action: #selector(RDEPUBAnnotationWebView.rd_highlight(_:))),
|
||
UIMenuItem(title: "批注", action: #selector(RDEPUBAnnotationWebView.rd_annotate(_:)))
|
||
]
|
||
webView.navigationDelegate = self
|
||
webView.scrollView.isScrollEnabled = false
|
||
webView.scrollView.showsHorizontalScrollIndicator = false
|
||
webView.scrollView.showsVerticalScrollIndicator = false
|
||
webView.scrollView.bounces = false
|
||
webView.scrollView.alwaysBounceHorizontal = false
|
||
webView.scrollView.alwaysBounceVertical = false
|
||
webView.scrollView.clipsToBounds = true
|
||
webView.scrollView.panGestureRecognizer.isEnabled = false
|
||
webView.scrollView.pinchGestureRecognizer?.isEnabled = false
|
||
webView.isOpaque = false
|
||
webView.backgroundColor = .clear
|
||
webView.clipsToBounds = true
|
||
if #available(iOS 16.4, *) {
|
||
webView.isInspectable = RDEPUBWebViewDebug.isInspectableEnabled
|
||
}
|
||
|
||
addSubview(webView)
|
||
webView.frame = bounds
|
||
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||
|
||
self.schemeHandler = schemeHandler
|
||
self.webView = webView
|
||
self.configuredPublicationKey = publicationKey
|
||
RDEPUBWebViewDebug.log(debugScope, message: "configured webView=\(RDEPUBWebViewDebug.webViewID(webView)) publicationKey=\(publicationKey)")
|
||
}
|
||
|
||
/// 处理选择菜单动作(拷贝/高亮/批注),通知委托并清除选择
|
||
func handleSelectionMenuAction(_ action: RDEPUBAnnotationMenuAction) {
|
||
delegate?.epubWebView(self, didRequestSelectionAction: action)
|
||
clearWebSelection()
|
||
}
|
||
|
||
/// 清除 WebView 中的文本选择
|
||
func clearWebSelection() {
|
||
webView?.evaluateJavaScript("window.getSelection && window.getSelection().removeAllRanges();")
|
||
}
|
||
|
||
/// 销毁 WebView,移除所有消息处理器和引用
|
||
func teardownWebView() {
|
||
cancelFixedLayoutReadyFallback()
|
||
if let webView {
|
||
RDEPUBWebViewDebug.log(debugScope, message: "teardown webView=\(RDEPUBWebViewDebug.webViewID(webView))")
|
||
}
|
||
webView?.navigationDelegate = nil
|
||
if let userContentController = webView?.configuration.userContentController {
|
||
RDEPUBJavaScriptBridge.messageNames.forEach {
|
||
userContentController.removeScriptMessageHandler(forName: $0)
|
||
}
|
||
}
|
||
(webView as? RDEPUBAnnotationWebView)?.onSelectionAction = nil
|
||
webView?.removeFromSuperview()
|
||
webView = nil
|
||
schemeHandler = nil
|
||
configuredPublicationKey = nil
|
||
currentLoadSignature = nil
|
||
}
|
||
}
|
||
|
||
extension UIColor {
|
||
/// 将 UIColor 转换为 CSS 十六进制颜色字符串(如 "#FF0000")
|
||
var ss_hexString: String {
|
||
var red: CGFloat = 0
|
||
var green: CGFloat = 0
|
||
var blue: CGFloat = 0
|
||
var alpha: CGFloat = 0
|
||
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
||
return String(format: "#%02X%02X%02X", Int(red * 255), Int(green * 255), Int(blue * 255))
|
||
}
|
||
}
|