- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
150 lines
7.1 KiB
Swift
150 lines
7.1 KiB
Swift
|
|
import Foundation
|
|
import WebKit
|
|
|
|
extension RDEPUBWebView {
|
|
|
|
func internalLocation(from url: URL) -> RDEPUBLocation? {
|
|
let href = url.path.removingPercentEncoding?.trimmingCharacters(in: CharacterSet(charactersIn: "/")) ?? ""
|
|
let fragment = url.fragment
|
|
guard !href.isEmpty || fragment != nil else { return nil }
|
|
return RDEPUBLocation(href: href, progression: 0, fragment: fragment)
|
|
}
|
|
|
|
func currentLocation(from body: [String: Any]) -> RDEPUBLocation {
|
|
let progression = (body["progression"] as? NSNumber)?.doubleValue ?? 0
|
|
let lastProgression = (body["lastProgression"] as? NSNumber)?.doubleValue
|
|
let fragment = body["fragment"] as? String
|
|
let href = body["href"] as? String ?? currentHref
|
|
return RDEPUBLocation(href: href, progression: progression, lastProgression: lastProgression, fragment: fragment)
|
|
}
|
|
}
|
|
|
|
extension RDEPUBWebView: WKNavigationDelegate {
|
|
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
|
|
RDEPUBWebViewDebug.logNavigationEvent(debugScope, webView: webView, event: "didStart", url: webView.url)
|
|
}
|
|
|
|
public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
|
RDEPUBWebViewDebug.logNavigationEvent(debugScope, webView: webView, event: "didCommit", url: webView.url)
|
|
}
|
|
|
|
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
RDEPUBWebViewDebug.logNavigationEvent(debugScope, webView: webView, event: "didFinish", url: webView.url)
|
|
applyPresentation()
|
|
}
|
|
|
|
public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
|
|
RDEPUBWebViewDebug.logNavigationEvent(debugScope, webView: webView, event: "processTerminated", url: webView.url)
|
|
}
|
|
|
|
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
|
RDEPUBWebViewDebug.logNavigationEvent(debugScope, webView: webView, event: "didFail", url: webView.url, error: error)
|
|
}
|
|
|
|
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
|
RDEPUBWebViewDebug.logNavigationEvent(debugScope, webView: webView, event: "didFailProvisional", url: webView.url, error: error)
|
|
}
|
|
|
|
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
|
RDEPUBWebViewDebug.logNavigationEvent(debugScope, webView: webView, event: "decidePolicy", url: navigationAction.request.url)
|
|
guard navigationAction.navigationType == .linkActivated,
|
|
let url = navigationAction.request.url else {
|
|
decisionHandler(.allow)
|
|
return
|
|
}
|
|
|
|
if url.scheme == RDEPUBResourceURLSchemeHandler.scheme,
|
|
let location = internalLocation(from: url) {
|
|
delegate?.epubWebView(self, didActivateInternalLink: location, fromSpineIndex: currentSpineIndex)
|
|
decisionHandler(.cancel)
|
|
return
|
|
}
|
|
|
|
if let scheme = url.scheme?.lowercased(), ["http", "https", "mailto", "tel"].contains(scheme) {
|
|
delegate?.epubWebView(self, didActivateExternalLink: url)
|
|
decisionHandler(.cancel)
|
|
return
|
|
}
|
|
|
|
decisionHandler(.allow)
|
|
}
|
|
}
|
|
|
|
extension RDEPUBWebView: WKScriptMessageHandler {
|
|
|
|
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
RDEPUBWebViewDebug.logMessage(debugScope, webView: message.webView, name: message.name, body: message.body)
|
|
switch message.name {
|
|
case RDEPUBJavaScriptBridgeMessage.progressionChanged.rawValue:
|
|
guard let body = message.body as? [String: Any] else { return }
|
|
delegate?.epubWebView(self, didUpdateLocation: currentLocation(from: body), spineIndex: currentSpineIndex)
|
|
case RDEPUBJavaScriptBridgeMessage.selectionChanged.rawValue:
|
|
if let body = message.body as? [String: Any],
|
|
let text = body["text"] as? String,
|
|
let rangeInfo = body["rangeInfo"] as? String {
|
|
let location = currentLocation(from: body)
|
|
let selection = RDEPUBSelection(location: location, text: text, rangeInfo: rangeInfo)
|
|
delegate?.epubWebView(self, didChangeSelection: selection, spineIndex: currentSpineIndex)
|
|
} else {
|
|
delegate?.epubWebView(self, didChangeSelection: nil, spineIndex: currentSpineIndex)
|
|
}
|
|
case RDEPUBJavaScriptBridgeMessage.internalLink.rawValue:
|
|
guard let body = message.body as? [String: Any],
|
|
let href = body["href"] as? String else { return }
|
|
let location = RDEPUBLocation(href: href, progression: 0)
|
|
delegate?.epubWebView(self, didActivateInternalLink: location, fromSpineIndex: currentSpineIndex)
|
|
case RDEPUBJavaScriptBridgeMessage.externalLink.rawValue:
|
|
guard let body = message.body as? [String: Any],
|
|
let urlString = body["url"] as? String,
|
|
let url = URL(string: urlString) else { return }
|
|
delegate?.epubWebView(self, didActivateExternalLink: url)
|
|
case RDEPUBJavaScriptBridgeMessage.javaScriptError.rawValue:
|
|
delegate?.epubWebView(self, didLogJavaScriptError: "\(message.body)")
|
|
case RDEPUBJavaScriptBridgeMessage.fixedLayoutReady.rawValue:
|
|
applyFixedPresentationIfNeeded {
|
|
self.refreshNativeDecorationsIfNeeded {
|
|
self.rendered()
|
|
}
|
|
}
|
|
case RDEPUBJavaScriptBridgeMessage.imageDidTap.rawValue:
|
|
guard let body = message.body as? [String: Any],
|
|
let src = body["src"] as? String else { return }
|
|
let sourceRect = sourceRect(from: body["rect"])
|
|
delegate?.epubWebView(self, didTapImageWithSource: src, sourceRect: sourceRect)
|
|
case RDEPUBJavaScriptBridgeMessage.footnoteDidTap.rawValue:
|
|
guard let body = message.body as? [String: Any],
|
|
let altText = body["alt"] as? String else { return }
|
|
let sourceRect = sourceRect(from: body["rect"])
|
|
delegate?.epubWebView(self, didTapFootnoteWithAltText: altText, sourceRect: sourceRect)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
private func sourceRect(from value: Any?) -> CGRect? {
|
|
guard let rectDict = value as? [String: Any] else { return nil }
|
|
return CGRect(
|
|
x: cgFloatValue(rectDict["x"]),
|
|
y: cgFloatValue(rectDict["y"]),
|
|
width: cgFloatValue(rectDict["width"]),
|
|
height: cgFloatValue(rectDict["height"])
|
|
)
|
|
}
|
|
|
|
private func cgFloatValue(_ value: Any?) -> CGFloat {
|
|
switch value {
|
|
case let number as NSNumber:
|
|
return CGFloat(truncating: number)
|
|
case let double as Double:
|
|
return CGFloat(double)
|
|
case let int as Int:
|
|
return CGFloat(int)
|
|
case let string as String:
|
|
return CGFloat(Double(string) ?? 0)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
}
|