// // FolioReaderWebView.swift // FolioReaderKit // // Created by Hans Seiffert on 21.09.16. // Copyright (c) 2016 Folio Reader. All rights reserved. // import UIKit /// The custom WebView used in each page open class FolioReaderWebView: UIWebView { var isColors = false var isShare = false var isOneWord = false fileprivate weak var readerContainer: FolioReaderContainer? fileprivate var readerConfig: FolioReaderConfig { guard let readerContainer = readerContainer else { return FolioReaderConfig() } return readerContainer.readerConfig } fileprivate var book: FRBook { guard let readerContainer = readerContainer else { return FRBook() } return readerContainer.book } fileprivate var folioReader: FolioReader { 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) } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - UIMenuController open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { guard readerConfig.useReaderMenuController else { return super.canPerformAction(action, withSender: sender) } if isShare { return false } else if isColors { return false } else { if action == #selector(highlight(_:)) || (action == #selector(define(_:)) && isOneWord) || (action == #selector(play(_:)) && (book.hasAudio || readerConfig.enableTTS)) || (action == #selector(share(_:)) && readerConfig.allowSharing) || (action == #selector(copy(_:)) && readerConfig.allowSharing) { return true } return false } } // MARK: - UIMenuController - Actions @objc func share(_ sender: UIMenuController) { let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let shareImage = UIAlertAction(title: self.readerConfig.localizedShareImageQuote, style: .default, handler: { (action) -> Void in if self.isShare { if let textToShare = self.js("getHighlightContent()") { self.folioReader.readerCenter?.presentQuoteShare(textToShare) } } else { if let textToShare = self.js("getSelectedText()") { self.folioReader.readerCenter?.presentQuoteShare(textToShare) self.clearTextSelection() } } self.setMenuVisible(false) }) let shareText = UIAlertAction(title: self.readerConfig.localizedShareTextQuote, style: .default) { (action) -> Void in if self.isShare { if let textToShare = self.js("getHighlightContent()") { self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame) } } else { if let textToShare = self.js("getSelectedText()") { self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame) } } self.setMenuVisible(false) } let cancel = UIAlertAction(title: self.readerConfig.localizedCancel, style: .cancel, handler: nil) alertController.addAction(shareImage) alertController.addAction(shareText) alertController.addAction(cancel) if let alert = alertController.popoverPresentationController { alert.sourceView = self.folioReader.readerCenter?.currentPage alert.sourceRect = sender.menuFrame } self.folioReader.readerCenter?.present(alertController, animated: true, completion: nil) } func colors(_ sender: UIMenuController?) { isColors = true createMenu(options: false) setMenuVisible(true) } func remove(_ sender: UIMenuController?) { if let removedId = js("removeThisHighlight()") { 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 = CGRectFromString(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 { return } 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 define(_ sender: UIMenuController?) { guard let selectedText = js("getSelectedText()") else { return } 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?) { self.folioReader.readerAudioPlayer?.play() self.clearTextSelection() } func setYellow(_ sender: UIMenuController?) { changeHighlightStyle(sender, style: .yellow) } func setGreen(_ sender: UIMenuController?) { changeHighlightStyle(sender, style: .green) } func setBlue(_ sender: UIMenuController?) { changeHighlightStyle(sender, style: .blue) } func setPink(_ sender: UIMenuController?) { changeHighlightStyle(sender, style: .pink) } func setUnderline(_ sender: UIMenuController?) { changeHighlightStyle(sender, style: .underline) } func changeHighlightStyle(_ sender: UIMenuController?, style: HighlightStyle) { self.folioReader.currentHighlightStyle = style.rawValue if let updateId = js("setHighlightStyle('\(HighlightStyle.classForStyle(style.rawValue))')") { Highlight.updateById(withConfiguration: self.readerConfig, highlightId: updateId, type: style) } } // MARK: - Create and show menu func createMenu(options: Bool) { guard (self.readerConfig.useReaderMenuController == true) else { return } isShare = options let colors = UIImage(readerImageNamed: "colors-marker") let share = UIImage(readerImageNamed: "share-marker") let remove = UIImage(readerImageNamed: "no-marker") let yellow = UIImage(readerImageNamed: "yellow-marker") let green = UIImage(readerImageNamed: "green-marker") let blue = UIImage(readerImageNamed: "blue-marker") let pink = UIImage(readerImageNamed: "pink-marker") let underline = UIImage(readerImageNamed: "underline-marker") let menuController = UIMenuController.shared let highlightItem = UIMenuItem(title: self.readerConfig.localizedHighlightMenu, action: #selector(highlight(_:))) let playAudioItem = UIMenuItem(title: self.readerConfig.localizedPlayMenu, action: #selector(play(_:))) let defineItem = UIMenuItem(title: self.readerConfig.localizedDefineMenu, action: #selector(define(_:))) let colorsItem = UIMenuItem(title: "C", image: colors) { [weak self] _ in self?.colors(menuController) } let shareItem = UIMenuItem(title: "S", image: share) { [weak self] _ in self?.share(menuController) } let removeItem = UIMenuItem(title: "R", image: remove) { [weak self] _ in self?.remove(menuController) } let yellowItem = UIMenuItem(title: "Y", image: yellow) { [weak self] _ in self?.setYellow(menuController) } let greenItem = UIMenuItem(title: "G", image: green) { [weak self] _ in self?.setGreen(menuController) } let blueItem = UIMenuItem(title: "B", image: blue) { [weak self] _ in self?.setBlue(menuController) } let pinkItem = UIMenuItem(title: "P", image: pink) { [weak self] _ in self?.setPink(menuController) } let underlineItem = UIMenuItem(title: "U", image: underline) { [weak self] _ in self?.setUnderline(menuController) } var menuItems = [shareItem] // menu on existing highlight if isShare { menuItems = [colorsItem, removeItem] if (self.readerConfig.allowSharing == true) { menuItems.append(shareItem) } } else if isColors { // menu for selecting highlight color menuItems = [yellowItem, greenItem, blueItem, pinkItem, underlineItem] } else { // default menu // PD: changed menuItems = [highlightItem/*, defineItem*/, shareItem] if self.book.hasAudio || self.readerConfig.enableTTS { menuItems.insert(playAudioItem, at: 0) } if (self.readerConfig.allowSharing == false) { menuItems.removeLast() } } menuController.menuItems = menuItems } open func setMenuVisible(_ menuVisible: Bool, animated: Bool = true, andRect rect: CGRect = CGRect.zero) { if !menuVisible && isShare || !menuVisible && isColors { isColors = false isShare = false } if menuVisible { if !rect.equalTo(CGRect.zero) { UIMenuController.shared.setTargetRect(rect, in: self) } } UIMenuController.shared.setMenuVisible(menuVisible, animated: animated) } // MARK: - Java Script Bridge @discardableResult open func js(_ script: String) -> String? { let callback = self.stringByEvaluatingJavaScript(from: script) if callback!.isEmpty { return nil } return callback } // MARK: WebView func clearTextSelection() { // Forces text selection clearing // @NOTE: this doesn't seem to always work self.isUserInteractionEnabled = false self.isUserInteractionEnabled = true } func setupScrollDirection() { switch self.readerConfig.scrollDirection { case .vertical, .defaultVertical, .horizontalWithVerticalContent: scrollView.isPagingEnabled = false paginationMode = .unpaginated scrollView.bounces = true break case .horizontal: scrollView.isPagingEnabled = true paginationMode = .leftToRight paginationBreakingMode = .page scrollView.bounces = false break } } }