// 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)) } }