2 // FolioReaderWebView.swift
5 // Created by Hans Seiffert on 21.09.16.
6 // Copyright (c) 2016 Folio Reader. All rights reserved.
11 /// The custom WebView used in each page
12 open class FolioReaderWebView: UIWebView {
17 fileprivate weak var readerContainer: FolioReaderContainer?
19 fileprivate var readerConfig: FolioReaderConfig {
20 guard let readerContainer = readerContainer else { return FolioReaderConfig() }
21 return readerContainer.readerConfig
24 fileprivate var book: FRBook {
25 guard let readerContainer = readerContainer else { return FRBook() }
26 return readerContainer.book
29 fileprivate var folioReader: FolioReader {
30 guard let readerContainer = readerContainer else { return FolioReader() }
31 return readerContainer.folioReader
34 override init(frame: CGRect) {
35 fatalError("use init(frame:readerConfig:book:) instead.")
38 init(frame: CGRect, readerContainer: FolioReaderContainer) {
39 self.readerContainer = readerContainer
41 super.init(frame: frame)
44 required public init?(coder aDecoder: NSCoder) {
45 fatalError("init(coder:) has not been implemented")
48 // MARK: - UIMenuController
50 open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
51 guard readerConfig.useReaderMenuController else {
52 return super.canPerformAction(action, withSender: sender)
60 if action == #selector(highlight(_:))
61 || (action == #selector(define(_:)) && isOneWord)
62 || (action == #selector(play(_:)) && (book.hasAudio || readerConfig.enableTTS))
63 || (action == #selector(share(_:)) && readerConfig.allowSharing)
64 || (action == #selector(copy(_:)) && readerConfig.allowSharing) {
71 // MARK: - UIMenuController - Actions
73 @objc func share(_ sender: UIMenuController) {
74 let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
76 let shareImage = UIAlertAction(title: self.readerConfig.localizedShareImageQuote, style: .default, handler: { (action) -> Void in
78 if let textToShare = self.js("getHighlightContent()") {
79 self.folioReader.readerCenter?.presentQuoteShare(textToShare)
82 if let textToShare = self.js("getSelectedText()") {
83 self.folioReader.readerCenter?.presentQuoteShare(textToShare)
85 self.clearTextSelection()
88 self.setMenuVisible(false)
91 let shareText = UIAlertAction(title: self.readerConfig.localizedShareTextQuote, style: .default) { (action) -> Void in
93 if let textToShare = self.js("getHighlightContent()") {
94 self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame)
97 if let textToShare = self.js("getSelectedText()") {
98 self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame)
101 self.setMenuVisible(false)
104 let cancel = UIAlertAction(title: self.readerConfig.localizedCancel, style: .cancel, handler: nil)
106 alertController.addAction(shareImage)
107 alertController.addAction(shareText)
108 alertController.addAction(cancel)
110 if let alert = alertController.popoverPresentationController {
111 alert.sourceView = self.folioReader.readerCenter?.currentPage
112 alert.sourceRect = sender.menuFrame
115 self.folioReader.readerCenter?.present(alertController, animated: true, completion: nil)
118 func colors(_ sender: UIMenuController?) {
120 createMenu(options: false)
124 func remove(_ sender: UIMenuController?) {
125 if let removedId = js("removeThisHighlight()") {
126 Highlight.removeById(withConfiguration: self.readerConfig, highlightId: removedId)
128 setMenuVisible(false)
131 @objc func highlight(_ sender: UIMenuController?) {
132 let highlightAndReturn = js("highlightString('\(HighlightStyle.classForStyle(self.folioReader.currentHighlightStyle))')")
133 let jsonData = highlightAndReturn?.data(using: String.Encoding.utf8)
136 let json = try JSONSerialization.jsonObject(with: jsonData!, options: []) as! NSArray
137 let dic = json.firstObject as! [String: String]
138 let rect = CGRectFromString(dic["rect"]!)
139 guard let startOffset = dic["startOffset"] else {
142 guard let endOffset = dic["endOffset"] else {
146 createMenu(options: true)
147 setMenuVisible(true, andRect: rect)
151 let html = js("getHTML()"),
152 let identifier = dic["id"],
153 let bookId = (self.book.name as NSString?)?.deletingPathExtension else {
157 let pageNumber = folioReader.readerCenter?.currentPageNumber ?? 0
158 let match = Highlight.MatchingHighlight(text: html, id: identifier, startOffset: startOffset, endOffset: endOffset, bookId: bookId, currentPage: pageNumber)
159 let highlight = Highlight.matchHighlight(match)
160 highlight?.persist(withConfiguration: self.readerConfig)
163 print("Could not receive JSON")
167 @objc func define(_ sender: UIMenuController?) {
168 guard let selectedText = js("getSelectedText()") else {
172 self.setMenuVisible(false)
173 self.clearTextSelection()
175 let vc = UIReferenceLibraryViewController(term: selectedText)
176 vc.view.tintColor = self.readerConfig.tintColor
177 guard let readerContainer = readerContainer else { return }
178 readerContainer.show(vc, sender: nil)
181 @objc func play(_ sender: UIMenuController?) {
182 self.folioReader.readerAudioPlayer?.play()
184 self.clearTextSelection()
187 func setYellow(_ sender: UIMenuController?) {
188 changeHighlightStyle(sender, style: .yellow)
191 func setGreen(_ sender: UIMenuController?) {
192 changeHighlightStyle(sender, style: .green)
195 func setBlue(_ sender: UIMenuController?) {
196 changeHighlightStyle(sender, style: .blue)
199 func setPink(_ sender: UIMenuController?) {
200 changeHighlightStyle(sender, style: .pink)
203 func setUnderline(_ sender: UIMenuController?) {
204 changeHighlightStyle(sender, style: .underline)
207 func changeHighlightStyle(_ sender: UIMenuController?, style: HighlightStyle) {
208 self.folioReader.currentHighlightStyle = style.rawValue
210 if let updateId = js("setHighlightStyle('\(HighlightStyle.classForStyle(style.rawValue))')") {
211 Highlight.updateById(withConfiguration: self.readerConfig, highlightId: updateId, type: style)
215 // MARK: - Create and show menu
217 func createMenu(options: Bool) {
218 guard (self.readerConfig.useReaderMenuController == true) else {
224 let colors = UIImage(readerImageNamed: "colors-marker")
225 let share = UIImage(readerImageNamed: "share-marker")
226 let remove = UIImage(readerImageNamed: "no-marker")
227 let yellow = UIImage(readerImageNamed: "yellow-marker")
228 let green = UIImage(readerImageNamed: "green-marker")
229 let blue = UIImage(readerImageNamed: "blue-marker")
230 let pink = UIImage(readerImageNamed: "pink-marker")
231 let underline = UIImage(readerImageNamed: "underline-marker")
233 let menuController = UIMenuController.shared
235 let highlightItem = UIMenuItem(title: self.readerConfig.localizedHighlightMenu, action: #selector(highlight(_:)))
236 let playAudioItem = UIMenuItem(title: self.readerConfig.localizedPlayMenu, action: #selector(play(_:)))
237 let defineItem = UIMenuItem(title: self.readerConfig.localizedDefineMenu, action: #selector(define(_:)))
238 let colorsItem = UIMenuItem(title: "C", image: colors) { [weak self] _ in
239 self?.colors(menuController)
241 let shareItem = UIMenuItem(title: "S", image: share) { [weak self] _ in
242 self?.share(menuController)
244 let removeItem = UIMenuItem(title: "R", image: remove) { [weak self] _ in
245 self?.remove(menuController)
247 let yellowItem = UIMenuItem(title: "Y", image: yellow) { [weak self] _ in
248 self?.setYellow(menuController)
250 let greenItem = UIMenuItem(title: "G", image: green) { [weak self] _ in
251 self?.setGreen(menuController)
253 let blueItem = UIMenuItem(title: "B", image: blue) { [weak self] _ in
254 self?.setBlue(menuController)
256 let pinkItem = UIMenuItem(title: "P", image: pink) { [weak self] _ in
257 self?.setPink(menuController)
259 let underlineItem = UIMenuItem(title: "U", image: underline) { [weak self] _ in
260 self?.setUnderline(menuController)
263 var menuItems = [shareItem]
265 // menu on existing highlight
267 menuItems = [colorsItem, removeItem]
268 if (self.readerConfig.allowSharing == true) {
269 menuItems.append(shareItem)
272 // menu for selecting highlight color
273 menuItems = [yellowItem, greenItem, blueItem, pinkItem, underlineItem]
277 menuItems = [highlightItem/*, defineItem*/, shareItem]
279 if self.book.hasAudio || self.readerConfig.enableTTS {
280 menuItems.insert(playAudioItem, at: 0)
283 if (self.readerConfig.allowSharing == false) {
284 menuItems.removeLast()
288 menuController.menuItems = menuItems
291 open func setMenuVisible(_ menuVisible: Bool, animated: Bool = true, andRect rect: CGRect = CGRect.zero) {
292 if !menuVisible && isShare || !menuVisible && isColors {
298 if !rect.equalTo(CGRect.zero) {
299 UIMenuController.shared.setTargetRect(rect, in: self)
303 UIMenuController.shared.setMenuVisible(menuVisible, animated: animated)
306 // MARK: - Java Script Bridge
308 @discardableResult open func js(_ script: String) -> String? {
309 let callback = self.stringByEvaluatingJavaScript(from: script)
310 if callback!.isEmpty { return nil }
316 func clearTextSelection() {
317 // Forces text selection clearing
318 // @NOTE: this doesn't seem to always work
320 self.isUserInteractionEnabled = false
321 self.isUserInteractionEnabled = true
324 func setupScrollDirection() {
325 switch self.readerConfig.scrollDirection {
326 case .vertical, .defaultVertical, .horizontalWithVerticalContent:
327 scrollView.isPagingEnabled = false
328 paginationMode = .unpaginated
329 scrollView.bounces = true
332 scrollView.isPagingEnabled = true
333 paginationMode = .leftToRight
334 paginationBreakingMode = .page
335 scrollView.bounces = false