diff --git a/Source/FolioReaderAddHighlightNote.swift b/Source/FolioReaderAddHighlightNote.swift index a4b7af710..b7cbc9e1b 100644 --- a/Source/FolioReaderAddHighlightNote.swift +++ b/Source/FolioReaderAddHighlightNote.swift @@ -67,7 +67,7 @@ class FolioReaderAddHighlightNote: UIViewController { if !highlightSaved && !isEditHighlight { guard let currentPage = folioReader.readerCenter?.currentPage else { return } - currentPage.webView?.js("removeThisHighlight()") + currentPage.webView?.js("removeThisHighlight()") { _ in } } } diff --git a/Source/FolioReaderAudioPlayer.swift b/Source/FolioReaderAudioPlayer.swift index 053fc929f..e59a8fd0f 100644 --- a/Source/FolioReaderAudioPlayer.swift +++ b/Source/FolioReaderAudioPlayer.swift @@ -176,7 +176,7 @@ open class FolioReaderAudioPlayer: NSObject { @objc func play() { if book.hasAudio { guard let currentPage = self.folioReader.readerCenter?.currentPage else { return } - currentPage.webView?.js("playAudio()") + currentPage.webView?.js("playAudio()") { _ in } } else { self.readCurrentSentence() } @@ -382,22 +382,27 @@ open class FolioReaderAudioPlayer: NSObject { } let playbackActiveClass = book.playbackActiveClass - guard let sentence = currentPage.webView?.js("getSentenceWithIndex('\(playbackActiveClass)')") else { - if (readerCenter.isLastPage() == true) { - self.stop() - } else { - readerCenter.changePageToNext() + + currentPage.webView?.js("getSentenceWithIndex('\(playbackActiveClass)')") { sentence in + guard let sentence = sentence else { + if (readerCenter.isLastPage() == true) { + self.stop() + } else { + readerCenter.changePageToNext() + } + return + } - - return - } - - guard let href = readerCenter.getCurrentChapter()?.href else { - return + + guard let href = readerCenter.getCurrentChapter()?.href else { + return + } + + // TODO QUESTION: The previous code made it possible to call `playText` with the parameter `href` being an empty string. Was that valid? should this logic be kept? + self.playText(href, text: sentence) + } - - // TODO QUESTION: The previous code made it possible to call `playText` with the parameter `href` being an empty string. Was that valid? should this logic be kept? - self.playText(href, text: sentence) + } func readCurrentSentence() { @@ -410,7 +415,7 @@ open class FolioReaderAudioPlayer: NSObject { if synthesizer.isSpeaking { stopSynthesizer(immediate: false, completion: { if let currentPage = self.folioReader.readerCenter?.currentPage { - currentPage.webView?.js("resetCurrentSentenceIndex()") + currentPage.webView?.js("resetCurrentSentenceIndex()") { _ in } } self.speakSentence() }) diff --git a/Source/FolioReaderCenter.swift b/Source/FolioReaderCenter.swift index 6c889f036..f8b5c5a66 100644 --- a/Source/FolioReaderCenter.swift +++ b/Source/FolioReaderCenter.swift @@ -648,12 +648,21 @@ open class FolioReaderCenter: UIViewController, UICollectionViewDelegate, UIColl } scrollScrubber?.setSliderVal() - - if let readingTime = currentPage.webView?.js("getReadingTime()") { - pageIndicatorView?.totalMinutes = Int(readingTime)! - } else { - pageIndicatorView?.totalMinutes = 0 + + currentPage.webView?.js("getReadingTime()") { readingTime in + + guard let readingTime = readingTime + else { + self.pageIndicatorView?.totalMinutes = 0 + return + + } + + self.pageIndicatorView?.totalMinutes = Int(readingTime)! + } + + pagesForCurrentPage(currentPage) delegate?.pageDidAppear?(currentPage) @@ -1075,60 +1084,66 @@ open class FolioReaderCenter: UIViewController, UICollectionViewDelegate, UIColl @objc func shareChapter(_ sender: UIBarButtonItem) { guard let currentPage = currentPage else { return } - if let chapterText = currentPage.webView?.js("getBodyText()") { + + currentPage.webView?.js("getBodyText()") { chapterText in + + guard let chapterText = chapterText else { return } + let htmlText = chapterText.replacingOccurrences(of: "[\\n\\r]+", with: "
", options: .regularExpression) - var subject = readerConfig.localizedShareChapterSubject + var subject = self.readerConfig.localizedShareChapterSubject var html = "" var text = "" var bookTitle = "" var chapterName = "" var authorName = "" var shareItems = [AnyObject]() - + // Get book title if let title = self.book.title { bookTitle = title subject += " “\(title)”" } - + // Get chapter name - if let chapter = getCurrentChapterName() { + if let chapter = self.getCurrentChapterName() { chapterName = chapter } - + // Get author name if let author = self.book.metadata.creators.first { authorName = author.name } - + // Sharing html and text html = "" html += "

\(htmlText)



" - html += "

"+readerConfig.localizedShareAllExcerptsFrom+"

" + html += "

"+self.readerConfig.localizedShareAllExcerptsFrom+"

" html += "\(bookTitle)
" - html += readerConfig.localizedShareBy+" \(authorName)
" - - if let bookShareLink = readerConfig.localizedShareWebLink { + html += self.readerConfig.localizedShareBy+" \(authorName)
" + + if let bookShareLink = self.readerConfig.localizedShareWebLink { html += "\(bookShareLink.absoluteString)" shareItems.append(bookShareLink as AnyObject) } - + html += "
" - text = "\(chapterName)\n\n“\(chapterText)” \n\n\(bookTitle) \n\(readerConfig.localizedShareBy) \(authorName)" - + text = "\(chapterName)\n\n“\(chapterText)” \n\n\(bookTitle) \n\(self.readerConfig.localizedShareBy) \(authorName)" + let act = FolioReaderSharingProvider(subject: subject, text: text, html: html) shareItems.insert(contentsOf: [act, "" as AnyObject], at: 0) - + let activityViewController = UIActivityViewController(activityItems: shareItems, applicationActivities: nil) activityViewController.excludedActivityTypes = [UIActivity.ActivityType.print, UIActivity.ActivityType.postToVimeo] - + // Pop style on iPad if let actv = activityViewController.popoverPresentationController { actv.barButtonItem = sender } - - present(activityViewController, animated: true, completion: nil) + + self.present(activityViewController, animated: true, completion: nil) + } + } /** diff --git a/Source/FolioReaderHighlightList.swift b/Source/FolioReaderHighlightList.swift index 16986a4c8..b2a804ee7 100644 --- a/Source/FolioReaderHighlightList.swift +++ b/Source/FolioReaderHighlightList.swift @@ -190,7 +190,7 @@ class FolioReaderHighlightList: UITableViewController { if (highlight.page == self.folioReader.readerCenter?.currentPageNumber), let page = self.folioReader.readerCenter?.currentPage { - Highlight.removeFromHTMLById(withinPage: page, highlightId: highlight.highlightId) // Remove from HTML + Highlight.removeFromHTMLById(withinPage: page, highlightId: highlight.highlightId) { _ in } // Remove from HTML } highlight.remove(withConfiguration: self.readerConfig) // Remove from Database diff --git a/Source/FolioReaderKit.swift b/Source/FolioReaderKit.swift index 96914b6ef..f6cd5bc79 100755 --- a/Source/FolioReaderKit.swift +++ b/Source/FolioReaderKit.swift @@ -185,7 +185,7 @@ extension FolioReader { if let readerCenter = self.readerCenter { UIView.animate(withDuration: 0.6, animations: { - _ = readerCenter.currentPage?.webView?.js("nightMode(\(self.nightMode))") + _ = readerCenter.currentPage?.webView?.js("nightMode(\(self.nightMode))") { _ in } readerCenter.pageIndicatorView?.reloadColors() readerCenter.configureNavBar() readerCenter.scrollScrubber?.reloadColors() @@ -210,7 +210,7 @@ extension FolioReader { } set (font) { self.defaults.set(font.rawValue, forKey: kCurrentFontFamily) - _ = self.readerCenter?.currentPage?.webView?.js("setFontName('\(font.cssIdentifier)')") + _ = self.readerCenter?.currentPage?.webView?.js("setFontName('\(font.cssIdentifier)')") { _ in } } } @@ -232,7 +232,7 @@ extension FolioReader { return } - currentPage.webView?.js("setFontSize('\(currentFontSize.cssIdentifier)')") + currentPage.webView?.js("setFontSize('\(currentFontSize.cssIdentifier)')") { _ in } } } diff --git a/Source/FolioReaderPage.swift b/Source/FolioReaderPage.swift index 5b013a0fd..3833bcfc2 100755 --- a/Source/FolioReaderPage.swift +++ b/Source/FolioReaderPage.swift @@ -9,6 +9,7 @@ import UIKit import SafariServices import MenuItemKit +import WebKit /// Protocol which is used from `FolioReaderPage`s. @objc public protocol FolioReaderPageDelegate: class { @@ -35,9 +36,9 @@ import MenuItemKit @objc optional func pageTap(_ recognizer: UITapGestureRecognizer) } -open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRecognizerDelegate { +open class FolioReaderPage: UICollectionViewCell, WKNavigationDelegate, UIGestureRecognizerDelegate { weak var delegate: FolioReaderPageDelegate? - weak var readerContainer: FolioReaderContainer? + var readerContainer: FolioReaderContainer? /// The index of the current page. Note: The index start at 1! open var pageNumber: Int! @@ -80,13 +81,12 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe if webView == nil { webView = FolioReaderWebView(frame: webViewFrame(), readerContainer: readerContainer) webView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] - webView?.dataDetectorTypes = .link webView?.scrollView.showsVerticalScrollIndicator = false webView?.scrollView.showsHorizontalScrollIndicator = false webView?.backgroundColor = .clear self.contentView.addSubview(webView!) } - webView?.delegate = self + webView?.navigationDelegate = self if colorView == nil { colorView = UIView() @@ -110,7 +110,7 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe deinit { webView?.scrollView.delegate = nil - webView?.delegate = nil + webView?.navigationDelegate = nil NotificationCenter.default.removeObserver(self) } @@ -186,129 +186,156 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe } return tempHtmlContent as String } - - // MARK: - UIWebView Delegate - - open func webViewDidFinishLoad(_ webView: UIWebView) { + + // MARK: - WKNavigationDelegate + + public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { guard let webView = webView as? FolioReaderWebView else { return } - + delegate?.pageWillLoad?(self) - + // Add the custom class based onClick listener self.setupClassBasedOnClickListeners() - + refreshPageMode() - + if self.readerConfig.enableTTS && !self.book.hasAudio { - webView.js("wrappingSentencesWithinPTags()") - + webView.js("wrappingSentencesWithinPTags()") { _ in } + if let audioPlayer = self.folioReader.readerAudioPlayer, (audioPlayer.isPlaying() == true) { audioPlayer.readCurrentSentence() } } - + let direction: ScrollDirection = self.folioReader.needsRTLChange ? .positive(withConfiguration: self.readerConfig) : .negative(withConfiguration: self.readerConfig) - + if (self.folioReader.readerCenter?.pageScrollDirection == direction && self.folioReader.readerCenter?.isScrolling == true && self.readerConfig.scrollDirection != .horizontalWithVerticalContent) { scrollPageToBottom() } - + UIView.animate(withDuration: 0.2, animations: {webView.alpha = 1}, completion: { finished in webView.isColors = false self.webView?.createMenu(options: false) }) - + delegate?.pageDidLoad?(self) + + } - - open func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool { + + public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + + let request = navigationAction.request guard let webView = webView as? FolioReaderWebView, let scheme = request.url?.scheme else { - return true + decisionHandler(WKNavigationActionPolicy.allow) + return } - - guard let url = request.url else { return false } - + + guard let url = request.url + else { + decisionHandler(WKNavigationActionPolicy.cancel) + return + } + if scheme == "highlight" || scheme == "highlight-with-note" { shouldShowBar = false - - guard let decoded = url.absoluteString.removingPercentEncoding else { return false } + + guard let decoded = url.absoluteString.removingPercentEncoding + else { + decisionHandler(WKNavigationActionPolicy.cancel) + return + } let index = decoded.index(decoded.startIndex, offsetBy: 12) let rect = NSCoder.cgRect(for: String(decoded[index...])) - + webView.createMenu(options: true) webView.setMenuVisible(true, andRect: rect) menuIsVisible = true - - return false + decisionHandler(WKNavigationActionPolicy.cancel) + return } else if scheme == "play-audio" { - guard let decoded = url.absoluteString.removingPercentEncoding else { return false } + guard let decoded = url.absoluteString.removingPercentEncoding + else { + decisionHandler(WKNavigationActionPolicy.cancel) + return + } let index = decoded.index(decoded.startIndex, offsetBy: 13) let playID = String(decoded[index...]) let chapter = self.folioReader.readerCenter?.getCurrentChapter() let href = chapter?.href ?? "" self.folioReader.readerAudioPlayer?.playAudio(href, fragmentID: playID) - - return false + + decisionHandler(WKNavigationActionPolicy.cancel) + return } else if scheme == "file" { - + let anchorFromURL = url.fragment - + // Handle internal url if !url.pathExtension.isEmpty { let pathComponent = (self.book.opfResource.href as NSString?)?.deletingLastPathComponent guard let base = ((pathComponent == nil || pathComponent?.isEmpty == true) ? self.book.name : pathComponent) else { - return true + decisionHandler(WKNavigationActionPolicy.allow) + return } - + let path = url.path let splitedPath = path.components(separatedBy: base) - + // Return to avoid crash if (splitedPath.count <= 1 || splitedPath[1].isEmpty) { - return true + decisionHandler(WKNavigationActionPolicy.allow) + return } - + let href = splitedPath[1].trimmingCharacters(in: CharacterSet(charactersIn: "/")) let hrefPage = (self.folioReader.readerCenter?.findPageByHref(href) ?? 0) + 1 - + if (hrefPage == pageNumber) { // Handle internal #anchor if anchorFromURL != nil { handleAnchor(anchorFromURL!, avoidBeginningAnchors: false, animated: true) - return false + decisionHandler(WKNavigationActionPolicy.cancel) + return } } else { self.folioReader.readerCenter?.changePageWith(href: href, animated: true) } - return false + decisionHandler(WKNavigationActionPolicy.cancel) + return } - + // Handle internal #anchor if anchorFromURL != nil { handleAnchor(anchorFromURL!, avoidBeginningAnchors: false, animated: true) - return false + decisionHandler(WKNavigationActionPolicy.cancel) + return } - - return true + + decisionHandler(WKNavigationActionPolicy.allow) + return + } else if scheme == "mailto" { print("Email") - return true - } else if url.absoluteString != "about:blank" && scheme.contains("http") && navigationType == .linkClicked { + decisionHandler(WKNavigationActionPolicy.allow) + return + } else if url.absoluteString != "about:blank" && scheme.contains("http") && navigationAction.navigationType == .linkActivated { let safariVC = SFSafariViewController(url: request.url!) safariVC.view.tintColor = self.readerConfig.tintColor self.folioReader.readerCenter?.present(safariVC, animated: true, completion: nil) - return false + decisionHandler(WKNavigationActionPolicy.cancel) + return } else { // Check if the url is a custom class based onClick listerner var isClassBasedOnClickListenerScheme = false for listener in self.readerConfig.classBasedOnClickListeners { - + if scheme == listener.schemeName, let absoluteURLString = request.url?.absoluteString, let range = absoluteURLString.range(of: "/clientX=") { @@ -323,21 +350,24 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe } } } - + if isClassBasedOnClickListenerScheme == false { // Try to open the url with the system if it wasn't a custom class based click listener if UIApplication.shared.canOpenURL(url) { UIApplication.shared.openURL(url) - return false + decisionHandler(WKNavigationActionPolicy.cancel) + return } } else { - return false + decisionHandler(WKNavigationActionPolicy.cancel) + return } } - - return true + + decisionHandler(WKNavigationActionPolicy.allow) + } - + fileprivate func getEventTouchPoint(fromPositionParameterString positionParameterString: String) -> CGPoint? { // Remove the parameter names: "/clientX=188&clientY=292" -> "188&292" var positionParameterString = positionParameterString.replacingOccurrences(of: "/clientX=", with: "") @@ -373,20 +403,24 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe self.delegate?.pageTap?(recognizer) if let _navigationController = self.folioReader.readerCenter?.navigationController, (_navigationController.isNavigationBarHidden == true) { - let selected = webView?.js("getSelectedText()") - guard (selected == nil || selected?.isEmpty == true) else { - return - } - - let delay = 0.4 * Double(NSEC_PER_SEC) // 0.4 seconds * nanoseconds per seconds - let dispatchTime = (DispatchTime.now() + (Double(Int64(delay)) / Double(NSEC_PER_SEC))) - DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: { - if (self.shouldShowBar == true && self.menuIsVisible == false) { - self.folioReader.readerCenter?.toggleBars() + webView?.js("getSelectedText()") { selected in + + guard (selected == nil || selected?.isEmpty == true) else { + return } - }) + + let delay = 0.4 * Double(NSEC_PER_SEC) // 0.4 seconds * nanoseconds per seconds + let dispatchTime = (DispatchTime.now() + (Double(Int64(delay)) / Double(NSEC_PER_SEC))) + + DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: { + if (self.shouldShowBar == true && self.menuIsVisible == false) { + self.folioReader.readerCenter?.toggleBars() + } + }) + } + } else if (self.readerConfig.shouldHideNavigationOnTap == true) { self.folioReader.readerCenter?.hideBars() self.menuIsVisible = false @@ -433,19 +467,21 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe */ open func handleAnchor(_ anchor: String, avoidBeginningAnchors: Bool, animated: Bool) { if !anchor.isEmpty { - let offset = getAnchorOffset(anchor) - - switch self.readerConfig.scrollDirection { - case .vertical, .defaultVertical: - let isBeginning = (offset < frame.forDirection(withConfiguration: self.readerConfig) * 0.5) - - if !avoidBeginningAnchors { - scrollPageToOffset(offset, animated: animated) - } else if avoidBeginningAnchors && !isBeginning { - scrollPageToOffset(offset, animated: animated) + getAnchorOffset(anchor) { offset in + + switch self.readerConfig.scrollDirection { + case .vertical, .defaultVertical: + let isBeginning = (offset < self.frame.forDirection(withConfiguration: self.readerConfig) * 0.5) + + if !avoidBeginningAnchors { + self.scrollPageToOffset(offset, animated: animated) + } else if avoidBeginningAnchors && !isBeginning { + self.scrollPageToOffset(offset, animated: animated) + } + case .horizontal, .horizontalWithVerticalContent: + self.scrollPageToOffset(offset, animated: animated) } - case .horizontal, .horizontalWithVerticalContent: - scrollPageToOffset(offset, animated: animated) + } } } @@ -458,13 +494,16 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe - parameter anchor: The #anchor id - returns: The element offset ready to scroll */ - func getAnchorOffset(_ anchor: String) -> CGFloat { + func getAnchorOffset(_ anchor: String, completion: @escaping ((CGFloat) -> ())) { let horizontal = self.readerConfig.scrollDirection == .horizontal - if let strOffset = webView?.js("getAnchorOffset('\(anchor)', \(horizontal.description))") { - return CGFloat((strOffset as NSString).floatValue) + + webView?.js("getAnchorOffset('\(anchor)', \(horizontal.description))") { strOffset in + guard let strOffset = strOffset else { + completion(CGFloat(0)) + return } + completion(CGFloat((strOffset as NSString).floatValue)) } - - return CGFloat(0) + } // MARK: Mark ID @@ -480,7 +519,7 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe } let playbackActiveClass = self.book.playbackActiveClass - currentPage.webView?.js("audioMarkID('\(playbackActiveClass)','\(identifier)')") + currentPage.webView?.js("audioMarkID('\(playbackActiveClass)','\(identifier)')") { _ in } } // MARK: UIMenu visibility @@ -494,11 +533,17 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe } if !webView.isShare && !webView.isColors { - if let result = webView.js("getSelectedText()") , result.components(separatedBy: " ").count == 1 { + + webView.js("getSelectedText()") { result in + + guard let result = result, result.components(separatedBy: " ").count == 1 else { + webView.isOneWord = false + return + } + webView.isOneWord = true webView.createMenu(options: false) - } else { - webView.isOneWord = false + } } @@ -508,35 +553,34 @@ open class FolioReaderPage: UICollectionViewCell, UIWebViewDelegate, UIGestureRe // MARK: ColorView fix for horizontal layout @objc func refreshPageMode() { guard let webView = webView else { return } - + if (self.folioReader.nightMode == true) { // omit create webView and colorView let script = "document.documentElement.offsetHeight" - let contentHeight = webView.stringByEvaluatingJavaScript(from: script) - let frameHeight = webView.frame.height - let lastPageHeight = frameHeight * CGFloat(webView.pageCount) - CGFloat(Double(contentHeight!)!) - colorView.frame = CGRect(x: webView.frame.width * CGFloat(webView.pageCount-1), y: webView.frame.height - lastPageHeight, width: webView.frame.width, height: lastPageHeight) - } else { - colorView.frame = CGRect.zero + + webView.js(script) { contentHeight in + + guard let contentHeight = Int(contentHeight ?? "") else { + self.colorView.frame = CGRect.zero + return + } + + let frameHeight = webView.frame.height +// let lastPageHeight = frameHeight * CGFloat(webView.pageCount) - CGFloat(Double(contentHeight!)!) +// colorView.frame = CGRect(x: webView.frame.width * CGFloat(webView.pageCount-1), y: webView.frame.height - lastPageHeight, width: webView.frame.width, height: lastPageHeight) + + } + } } + // MARK: - Class based click listener fileprivate func setupClassBasedOnClickListeners() { for listener in self.readerConfig.classBasedOnClickListeners { - self.webView?.js("addClassBasedOnClickListener(\"\(listener.schemeName)\", \"\(listener.querySelector)\", \"\(listener.attributeName)\", \"\(listener.selectAll)\")"); + self.webView?.js("addClassBasedOnClickListener(\"\(listener.schemeName)\", \"\(listener.querySelector)\", \"\(listener.attributeName)\", \"\(listener.selectAll)\")") { _ in } } } - // MARK: - Public Java Script injection - - /** - Runs a JavaScript script and returns it result. The result of running the JavaScript script passed in the script parameter, or nil if the script fails. - - - returns: The result of running the JavaScript script passed in the script parameter, or nil if the script fails. - */ - open func performJavaScript(_ javaScriptCode: String) -> String? { - return webView?.js(javaScriptCode) - } } diff --git a/Source/FolioReaderPageIndicator.swift b/Source/FolioReaderPageIndicator.swift index fc1960556..60fb1ca16 100644 --- a/Source/FolioReaderPageIndicator.swift +++ b/Source/FolioReaderPageIndicator.swift @@ -95,14 +95,14 @@ class FolioReaderPageIndicator: UIView { pagesLabel.text = " \(pagesRemaining) " + self.readerConfig.localizedReaderManyPagesLeft } - let minutesRemaining = Int(ceil(CGFloat((pagesRemaining * totalMinutes)/totalPages))) - if minutesRemaining > 1 { - minutesLabel.text = "\(minutesRemaining) " + self.readerConfig.localizedReaderManyMinutes+" ·" - } else if minutesRemaining == 1 { - minutesLabel.text = self.readerConfig.localizedReaderOneMinute+" ·" - } else { - minutesLabel.text = self.readerConfig.localizedReaderLessThanOneMinute+" ·" - } +// let minutesRemaining = Int(ceil(CGFloat((pagesRemaining * totalMinutes)/totalPages))) +// if minutesRemaining > 1 { +// minutesLabel.text = "\(minutesRemaining) " + self.readerConfig.localizedReaderManyMinutes+" ·" +// } else if minutesRemaining == 1 { +// minutesLabel.text = self.readerConfig.localizedReaderOneMinute+" ·" +// } else { +// minutesLabel.text = self.readerConfig.localizedReaderLessThanOneMinute+" ·" +// } reloadView(updateShadow: false) } diff --git a/Source/FolioReaderPlayerMenu.swift b/Source/FolioReaderPlayerMenu.swift index 8b2d24434..fb7fda350 100644 --- a/Source/FolioReaderPlayerMenu.swift +++ b/Source/FolioReaderPlayerMenu.swift @@ -267,7 +267,7 @@ class FolioReaderPlayerMenu: UIViewController, SMSegmentViewDelegate, UIGestureR // update the current page style if let currentPage = self.folioReader.readerCenter?.currentPage { - currentPage.webView?.js("setMediaOverlayStyle(\"\(self.folioReader.currentMediaOverlayStyle.className())\")") + currentPage.webView?.js("setMediaOverlayStyle(\"\(self.folioReader.currentMediaOverlayStyle.className())\")") { _ in } } } diff --git a/Source/FolioReaderWebView.swift b/Source/FolioReaderWebView.swift index 9c3b7531a..0ec2f26a7 100644 --- a/Source/FolioReaderWebView.swift +++ b/Source/FolioReaderWebView.swift @@ -6,10 +6,13 @@ // Copyright (c) 2016 Folio Reader. All rights reserved. // -import UIKit +import WebKit + +public typealias JSCallback = ((String?) ->()) /// The custom WebView used in each page -open class FolioReaderWebView: UIWebView { +open class FolioReaderWebView: WKWebView { + var isColors = false var isShare = false var isOneWord = false @@ -30,15 +33,18 @@ open class FolioReaderWebView: UIWebView { guard let readerContainer = readerContainer else { return FolioReader() } return readerContainer.folioReader } - - override init(frame: CGRect) { - fatalError("use init(frame:readerConfig:book:) instead.") - } - + init(frame: CGRect, readerContainer: FolioReaderContainer) { self.readerContainer = readerContainer - - super.init(frame: frame) + + let configuration = WKWebViewConfiguration() + if #available(iOS 10.0, *) { + configuration.dataDetectorTypes = .link + } else { + // Fallback on earlier versions + assertionFailure("unsupported iOS version") + } + super.init(frame: frame, configuration: configuration) } required public init?(coder aDecoder: NSCoder) { @@ -77,13 +83,15 @@ open class FolioReaderWebView: UIWebView { let shareImage = UIAlertAction(title: self.readerConfig.localizedShareImageQuote, style: .default, handler: { (action) -> Void in if self.isShare { - if let textToShare = self.js("getHighlightContent()") { + self.js("getHighlightContent()") { textToShare in + guard let textToShare = textToShare else { return } self.folioReader.readerCenter?.presentQuoteShare(textToShare) } } else { - if let textToShare = self.js("getSelectedText()") { + self.js("getSelectedText()") { textToShare in + guard let textToShare = textToShare else { return } self.folioReader.readerCenter?.presentQuoteShare(textToShare) - + self.clearTextSelection() } } @@ -92,11 +100,13 @@ open class FolioReaderWebView: UIWebView { let shareText = UIAlertAction(title: self.readerConfig.localizedShareTextQuote, style: .default) { (action) -> Void in if self.isShare { - if let textToShare = self.js("getHighlightContent()") { + self.js("getHighlightContent()") { textToShare in + guard let textToShare = textToShare else { return } self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame) } } else { - if let textToShare = self.js("getSelectedText()") { + self.js("getSelectedText()") { textToShare in + guard let textToShare = textToShare else { return } self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame) } } @@ -124,92 +134,117 @@ open class FolioReaderWebView: UIWebView { } func remove(_ sender: UIMenuController?) { - if let removedId = js("removeThisHighlight()") { + + js("removeThisHighlight()") { removedId in + guard let removedId = removedId else { return } Highlight.removeById(withConfiguration: self.readerConfig, highlightId: removedId) } + setMenuVisible(false) } @objc func highlight(_ sender: UIMenuController?) { - let highlightAndReturn = js("highlightString('\(HighlightStyle.classForStyle(self.folioReader.currentHighlightStyle))')") - let jsonData = highlightAndReturn?.data(using: String.Encoding.utf8) - - do { - let json = try JSONSerialization.jsonObject(with: jsonData!, options: []) as! NSArray - let dic = json.firstObject as! [String: String] - let rect = NSCoder.cgRect(for: dic["rect"]!) - guard let startOffset = dic["startOffset"] else { - return - } - guard let endOffset = dic["endOffset"] else { - return - } - - createMenu(options: true) - setMenuVisible(true, andRect: rect) - - // Persist - guard - let html = js("getHTML()"), - let identifier = dic["id"], - let bookId = (self.book.name as NSString?)?.deletingPathExtension else { + + js("highlightString('\(HighlightStyle.classForStyle(self.folioReader.currentHighlightStyle))')") { highlightAndReturn in + let jsonData = highlightAndReturn?.data(using: String.Encoding.utf8) + + do { + let json = try JSONSerialization.jsonObject(with: jsonData!, options: []) as! NSArray + let dic = json.firstObject as! [String: String] + let rect = NSCoder.cgRect(for: dic["rect"]!) + guard let startOffset = dic["startOffset"] else { + return + } + guard let endOffset = dic["endOffset"] else { return + } + + self.createMenu(options: true) + self.setMenuVisible(true, andRect: rect) + + + // Persist + self.js("getHTML()") { html in + + guard let html = html, let identifier = dic["id"], let bookId = (self.book.name as NSString?)?.deletingPathExtension + else { + return + } + let pageNumber = self.folioReader.readerCenter?.currentPageNumber ?? 0 + let match = Highlight.MatchingHighlight(text: html, id: identifier, startOffset: startOffset, endOffset: endOffset, bookId: bookId, currentPage: pageNumber) + let highlight = Highlight.matchHighlight(match) + highlight?.persist(withConfiguration: self.readerConfig) + } + + } catch { + print("Could not receive JSON") } - - let pageNumber = folioReader.readerCenter?.currentPageNumber ?? 0 - let match = Highlight.MatchingHighlight(text: html, id: identifier, startOffset: startOffset, endOffset: endOffset, bookId: bookId, currentPage: pageNumber) - let highlight = Highlight.matchHighlight(match) - highlight?.persist(withConfiguration: self.readerConfig) - - } catch { - print("Could not receive JSON") + } + } @objc func highlightWithNote(_ sender: UIMenuController?) { - let highlightAndReturn = js("highlightStringWithNote('\(HighlightStyle.classForStyle(self.folioReader.currentHighlightStyle))')") - let jsonData = highlightAndReturn?.data(using: String.Encoding.utf8) - do { - let json = try JSONSerialization.jsonObject(with: jsonData!, options: []) as! NSArray - let dic = json.firstObject as! [String: String] - guard let startOffset = dic["startOffset"] else { return } - guard let endOffset = dic["endOffset"] else { return } - - self.clearTextSelection() + js("highlightStringWithNote('\(HighlightStyle.classForStyle(self.folioReader.currentHighlightStyle))')") { highlightAndReturn in - guard let html = js("getHTML()") else { return } - guard let identifier = dic["id"] else { return } - guard let bookId = (self.book.name as NSString?)?.deletingPathExtension else { return } + let jsonData = highlightAndReturn?.data(using: String.Encoding.utf8) - let pageNumber = folioReader.readerCenter?.currentPageNumber ?? 0 - let match = Highlight.MatchingHighlight(text: html, id: identifier, startOffset: startOffset, endOffset: endOffset, bookId: bookId, currentPage: pageNumber) - if let highlight = Highlight.matchHighlight(match) { - self.folioReader.readerCenter?.presentAddHighlightNote(highlight, edit: false) + do { + let json = try JSONSerialization.jsonObject(with: jsonData!, options: []) as! NSArray + let dic = json.firstObject as! [String: String] + guard let startOffset = dic["startOffset"] else { return } + guard let endOffset = dic["endOffset"] else { return } + + self.clearTextSelection() + + self.js("getHTML()") { html in + + guard let html = html, let identifier = dic["id"], let bookId = (self.book.name as NSString?)?.deletingPathExtension + else { + return + } + + let pageNumber = self.folioReader.readerCenter?.currentPageNumber ?? 0 + let match = Highlight.MatchingHighlight(text: html, id: identifier, startOffset: startOffset, endOffset: endOffset, bookId: bookId, currentPage: pageNumber) + if let highlight = Highlight.matchHighlight(match) { + self.folioReader.readerCenter?.presentAddHighlightNote(highlight, edit: false) + + } + + } + } catch { + print("Could not receive JSON") } - } catch { - print("Could not receive JSON") + } } @objc func updateHighlightNote (_ sender: UIMenuController?) { - guard let highlightId = js("getHighlightId()") else { return } - guard let highlightNote = Highlight.getById(withConfiguration: readerConfig, highlightId: highlightId) else { return } - self.folioReader.readerCenter?.presentAddHighlightNote(highlightNote, edit: true) + + js("getHighlightId()") { highlightId in + guard let highlightId = highlightId, let highlightNote = Highlight.getById(withConfiguration: self.readerConfig, highlightId: highlightId) else { return } + self.folioReader.readerCenter?.presentAddHighlightNote(highlightNote, edit: true) + } + } @objc func define(_ sender: UIMenuController?) { - guard let selectedText = js("getSelectedText()") else { - return + + js("getSelectedText()") { selectedText in + + guard let selectedText = selectedText else { return } + + self.setMenuVisible(false) + self.clearTextSelection() + + let vc = UIReferenceLibraryViewController(term: selectedText) + vc.view.tintColor = self.readerConfig.tintColor + guard let readerContainer = self.readerContainer else { return } + readerContainer.show(vc, sender: nil) + } - self.setMenuVisible(false) - self.clearTextSelection() - - let vc = UIReferenceLibraryViewController(term: selectedText) - vc.view.tintColor = self.readerConfig.tintColor - guard let readerContainer = readerContainer else { return } - readerContainer.show(vc, sender: nil) } @objc func play(_ sender: UIMenuController?) { @@ -241,7 +276,8 @@ open class FolioReaderWebView: UIWebView { func changeHighlightStyle(_ sender: UIMenuController?, style: HighlightStyle) { self.folioReader.currentHighlightStyle = style.rawValue - if let updateId = js("setHighlightStyle('\(HighlightStyle.classForStyle(style.rawValue))')") { + js("setHighlightStyle('\(HighlightStyle.classForStyle(style.rawValue))')") { updateId in + guard let updateId = updateId else { return } Highlight.updateById(withConfiguration: self.readerConfig, highlightId: updateId, type: style) } @@ -346,10 +382,12 @@ open class FolioReaderWebView: UIWebView { // MARK: - Java Script Bridge - @discardableResult open func js(_ script: String) -> String? { - let callback = self.stringByEvaluatingJavaScript(from: script) - if callback!.isEmpty { return nil } - return callback + open func js(_ script: String, completion: @escaping JSCallback) { + + self.evaluateJavaScript(script) { (result, error) in + completion(result as? String) + } + } // MARK: WebView @@ -366,13 +404,13 @@ open class FolioReaderWebView: UIWebView { switch self.readerConfig.scrollDirection { case .vertical, .defaultVertical, .horizontalWithVerticalContent: scrollView.isPagingEnabled = false - paginationMode = .unpaginated + //paginationMode = .unpaginated scrollView.bounces = true break case .horizontal: scrollView.isPagingEnabled = true - paginationMode = .leftToRight - paginationBreakingMode = .page + //paginationMode = .leftToRight + //paginationBreakingMode = .page scrollView.bounces = false break } diff --git a/Source/Models/Highlight+Helper.swift b/Source/Models/Highlight+Helper.swift index ac6a9aaf5..e37f8dd52 100644 --- a/Source/Models/Highlight+Helper.swift +++ b/Source/Models/Highlight+Helper.swift @@ -289,15 +289,11 @@ extension Highlight { /// - page: The page containing the HTML. /// - highlightId: The ID to be removed /// - Returns: The removed id - @discardableResult public static func removeFromHTMLById(withinPage page: FolioReaderPage?, highlightId: String) -> String? { - guard let currentPage = page else { return nil } + public static func removeFromHTMLById(withinPage page: FolioReaderPage?, highlightId: String, completion: @escaping JSCallback) { + guard let currentPage = page else { return } + + currentPage.webView?.js("removeHighlightById('\(highlightId)')", completion: completion) - if let removedId = currentPage.webView?.js("removeHighlightById('\(highlightId)')") { - return removedId - } else { - print("Error removing Highlight from page") - return nil - } } /**