2 // FolioReaderCenter.swift
5 // Created by Heberti Almeida on 08/04/15.
6 // Copyright (c) 2015 Folio Reader. All rights reserved.
10 import ZFDragableModalTransition
12 /// Protocol which is used from `FolioReaderCenter`s.
13 @objc public protocol FolioReaderCenterDelegate: class {
15 /// Notifies that a page appeared. This is triggered when a page is chosen and displayed.
17 /// - Parameter page: The appeared page
18 @objc optional func pageDidAppear(_ page: FolioReaderPage)
20 /// Passes and returns the HTML content as `String`. Implement this method if you want to modify the HTML content of a `FolioReaderPage`.
23 /// - page: The `FolioReaderPage`.
24 /// - htmlContent: The current HTML content as `String`.
25 /// - Returns: The adjusted HTML content as `String`. This is the content which will be loaded into the given `FolioReaderPage`.
26 @objc optional func htmlContentForPage(_ page: FolioReaderPage, htmlContent: String) -> String
28 /// Notifies that a page changed. This is triggered when collection view cell is changed.
30 /// - Parameter pageNumber: The appeared page item
31 @objc optional func pageItemChanged(_ pageNumber: Int)
35 /// The base reader class
36 open class FolioReaderCenter: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
38 /// This delegate receives the events from the current `FolioReaderPage`s delegate.
39 open weak var delegate: FolioReaderCenterDelegate?
41 /// This delegate receives the events from current page
42 open weak var pageDelegate: FolioReaderPageDelegate?
44 /// The base reader container
45 open weak var readerContainer: FolioReaderContainer?
47 /// The current visible page on reader
48 open fileprivate(set) var currentPage: FolioReaderPage?
50 /// The collection view with pages
51 open var collectionView: UICollectionView!
53 let collectionViewLayout = UICollectionViewFlowLayout()
54 var loadingView: UIActivityIndicatorView!
56 var totalPages: Int = 0
57 var tempFragment: String?
58 var animator: ZFModalTransitionAnimator!
59 var pageIndicatorView: FolioReaderPageIndicator?
60 var pageIndicatorHeight: CGFloat = 20
61 var recentlyScrolled = false
62 var recentlyScrolledDelay = 2.0 // 2 second delay until we clear recentlyScrolled
63 var recentlyScrolledTimer: Timer!
64 var scrollScrubber: ScrollScrubber?
65 var activityIndicator = UIActivityIndicatorView()
66 var isScrolling = false
67 var pageScrollDirection = ScrollDirection()
68 var nextPageNumber: Int = 0
69 var previousPageNumber: Int = 0
70 var currentPageNumber: Int = 0
71 var pageWidth: CGFloat = 0.0
72 var pageHeight: CGFloat = 0.0
76 fileprivate var screenBounds: CGRect!
77 fileprivate var pointNow = CGPoint.zero
78 fileprivate var pageOffsetRate: CGFloat = 0
79 fileprivate var tempReference: FRTocReference?
80 fileprivate var isFirstLoad = true
81 fileprivate var currentWebViewScrollPositions = [Int: CGPoint]()
82 fileprivate var currentOrientation: UIInterfaceOrientation?
83 fileprivate var changingChapter = false
85 fileprivate var readerConfig: FolioReaderConfig {
86 guard let readerContainer = readerContainer else { return FolioReaderConfig() }
87 return readerContainer.readerConfig
90 fileprivate var book: FRBook {
91 guard let readerContainer = readerContainer else { return FRBook() }
92 return readerContainer.book
95 fileprivate var folioReader: FolioReader {
96 guard let readerContainer = readerContainer else { return FolioReader() }
97 return readerContainer.folioReader
102 init(withContainer readerContainer: FolioReaderContainer) {
103 self.readerContainer = readerContainer
104 super.init(nibName: nil, bundle: Bundle.frameworkBundle())
106 self.initialization()
109 required public init?(coder aDecoder: NSCoder) {
110 fatalError("This class doesn't support NSCoding.")
114 Common Initialization
116 fileprivate func initialization() {
118 if (self.readerConfig.hideBars == true) {
119 self.pageIndicatorHeight = 0
122 self.totalPages = book.spine.spineReferences.count
125 let style: UIActivityIndicatorViewStyle = folioReader.isNight(.white, .gray)
126 loadingView = UIActivityIndicatorView(activityIndicatorStyle: style)
127 loadingView.hidesWhenStopped = true
128 loadingView.startAnimating()
129 self.view.addSubview(loadingView)
132 // MARK: - View life cicle
134 override open func viewDidLoad() {
137 screenBounds = self.getScreenBounds()
139 setPageSize(UIApplication.shared.statusBarOrientation)
142 collectionViewLayout.sectionInset = UIEdgeInsets.zero
143 collectionViewLayout.minimumLineSpacing = 0
144 collectionViewLayout.minimumInteritemSpacing = 0
145 collectionViewLayout.scrollDirection = .direction(withConfiguration: self.readerConfig)
147 let background = folioReader.isNight(self.readerConfig.nightModeBackground, UIColor.white)
148 view.backgroundColor = background
151 collectionView = UICollectionView(frame: screenBounds, collectionViewLayout: collectionViewLayout)
152 collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
153 collectionView.delegate = self
154 collectionView.dataSource = self
155 collectionView.isPagingEnabled = true
156 collectionView.showsVerticalScrollIndicator = false
157 collectionView.showsHorizontalScrollIndicator = false
158 collectionView.backgroundColor = background
159 collectionView.decelerationRate = UIScrollViewDecelerationRateFast
160 enableScrollBetweenChapters(scrollEnabled: true)
161 view.addSubview(collectionView)
163 if #available(iOS 11.0, *) {
164 collectionView.contentInsetAdjustmentBehavior = .never
167 // Activity Indicator
168 self.activityIndicator.activityIndicatorViewStyle = .gray
169 self.activityIndicator.hidesWhenStopped = true
170 self.activityIndicator = UIActivityIndicatorView(frame: CGRect(x: screenBounds.size.width/2, y: screenBounds.size.height/2, width: 30, height: 30))
171 self.activityIndicator.backgroundColor = UIColor.gray
172 self.view.addSubview(self.activityIndicator)
173 self.view.bringSubview(toFront: self.activityIndicator)
175 if #available(iOS 10.0, *) {
176 collectionView.isPrefetchingEnabled = false
179 // Register cell classes
180 collectionView?.register(FolioReaderPage.self, forCellWithReuseIdentifier: kReuseCellIdentifier)
182 // Configure navigation bar and layout
183 automaticallyAdjustsScrollViewInsets = false
184 extendedLayoutIncludesOpaqueBars = true
187 // Page indicator view
188 if (self.readerConfig.hidePageIndicator == false) {
189 let frame = self.frameForPageIndicatorView()
190 pageIndicatorView = FolioReaderPageIndicator(frame: frame, readerConfig: readerConfig, folioReader: folioReader)
191 if let pageIndicatorView = pageIndicatorView {
192 view.addSubview(pageIndicatorView)
196 guard let readerContainer = readerContainer else { return }
197 self.scrollScrubber = ScrollScrubber(frame: frameForScrollScrubber(), withReaderContainer: readerContainer)
198 self.scrollScrubber?.delegate = self
199 if let scrollScrubber = scrollScrubber {
200 view.addSubview(scrollScrubber.slider)
204 override open func viewWillAppear(_ animated: Bool) {
205 super.viewWillAppear(animated)
210 pagesForCurrentPage(currentPage)
211 pageIndicatorView?.reloadView(updateShadow: true)
214 override open func viewDidLayoutSubviews() {
215 super.viewDidLayoutSubviews()
217 screenBounds = self.getScreenBounds()
218 loadingView.center = view.center
220 setPageSize(UIApplication.shared.statusBarOrientation)
221 updateSubviewFrames()
227 Enable or disable the scrolling between chapters (`FolioReaderPage`s). If this is enabled it's only possible to read the current chapter. If another chapter should be displayed is has to be triggered programmatically with `changePageWith`.
229 - parameter scrollEnabled: `Bool` which enables or disables the scrolling between `FolioReaderPage`s.
231 open func enableScrollBetweenChapters(scrollEnabled: Bool) {
232 self.collectionView.isScrollEnabled = scrollEnabled
235 fileprivate func updateSubviewFrames() {
236 self.pageIndicatorView?.frame = self.frameForPageIndicatorView()
237 self.scrollScrubber?.frame = self.frameForScrollScrubber()
240 fileprivate func frameForPageIndicatorView() -> CGRect {
241 var bounds = CGRect(x: 0, y: screenBounds.size.height-pageIndicatorHeight, width: screenBounds.size.width, height: pageIndicatorHeight)
243 if #available(iOS 11.0, *) {
244 bounds.size.height = bounds.size.height + view.safeAreaInsets.bottom
250 fileprivate func frameForScrollScrubber() -> CGRect {
251 let scrubberY: CGFloat = ((self.readerConfig.shouldHideNavigationOnTap == true || self.readerConfig.hideBars == true) ? 50 : 74)
252 return CGRect(x: self.pageWidth + 10, y: scrubberY, width: 40, height: (self.pageHeight - 100))
255 func configureNavBar() {
256 let greenColor = UIColor(red:0.00, green:0.51, blue:0.53, alpha:1.00)
257 let navBackground = folioReader.isNight(self.readerConfig.nightModeMenuBackground, greenColor)
258 let tintColor = folioReader.isNight(greenColor, UIColor.white)// readerConfig.tintColor
259 let navText = tintColor// folioReader.isNight(UIColor.white, UIColor.black)
260 let font = UIFont(name: "Avenir-Light", size: 17)!
261 setTranslucentNavigation(color: navBackground, tintColor: tintColor, titleColor: navText, andFont: font)
264 func configureNavBarButtons() {
267 let shareIcon = UIImage(readerImageNamed: "icon-navbar-share")//.ignoreSystemTint(withConfiguration: self.readerConfig)
268 let audioIcon = UIImage(readerImageNamed: "icon-navbar-tts")//?.ignoreSystemTint(withConfiguration: self.readerConfig) //man-speech-icon
269 let closeIcon = UIImage(readerImageNamed: "icon-navbar-close")//?.ignoreSystemTint(withConfiguration: self.readerConfig)
270 let tocIcon = UIImage(readerImageNamed: "icon-navbar-toc")//?.ignoreSystemTint(withConfiguration: self.readerConfig)
271 let fontIcon = UIImage(readerImageNamed: "icon-navbar-font")//?.ignoreSystemTint(withConfiguration: self.readerConfig)
272 let space = 70 as CGFloat
274 let menu = UIBarButtonItem(image: closeIcon, style: .plain, target: self, action:#selector(closeReader(_:)))
275 let toc = UIBarButtonItem(image: tocIcon, style: .plain, target: self, action:#selector(presentChapterList(_:)))
277 navigationItem.leftBarButtonItems = [menu, toc]
279 var rightBarIcons = [UIBarButtonItem]()
281 if (self.readerConfig.allowSharing == true) {
282 rightBarIcons.append(UIBarButtonItem(image: shareIcon, style: .plain, target: self, action:#selector(shareChapter(_:))))
285 if self.book.hasAudio || self.readerConfig.enableTTS {
286 rightBarIcons.append(UIBarButtonItem(image: audioIcon, style: .plain, target: self, action:#selector(presentPlayerMenu(_:))))
289 let font = UIBarButtonItem(image: fontIcon, style: .plain, target: self, action: #selector(presentFontsMenu))
292 rightBarIcons.append(contentsOf: [font])
293 navigationItem.rightBarButtonItems = rightBarIcons
295 if(self.readerConfig.displayTitle){
296 navigationItem.title = book.title
301 self.loadingView.stopAnimating()
302 self.totalPages = book.spine.spineReferences.count
304 self.collectionView.reloadData()
305 self.configureNavBarButtons()
306 self.setCollectionViewProgressiveDirection()
308 if self.readerConfig.loadSavedPositionForCurrentBook {
309 guard let position = folioReader.savedPositionForCurrentBook, let pageNumber = position["pageNumber"] as? Int, pageNumber > 0 else {
310 self.currentPageNumber = 1
314 self.changePageWith(page: pageNumber)
315 self.currentPageNumber = pageNumber
319 // MARK: Change page progressive direction
321 private func transformViewForRTL(_ view: UIView?) {
322 if folioReader.needsRTLChange {
323 view?.transform = CGAffineTransform(scaleX: -1, y: 1)
325 view?.transform = CGAffineTransform.identity
329 func setCollectionViewProgressiveDirection() {
330 self.transformViewForRTL(self.collectionView)
333 func setPageProgressiveDirection(_ page: FolioReaderPage) {
334 self.transformViewForRTL(page)
337 // MARK: Change layout orientation
339 /// Get internal page offset before layout change
340 private func updatePageOffsetRate() {
341 guard let currentPage = self.currentPage, let webView = currentPage.webView else {
345 let pageScrollView = webView.scrollView
346 let contentSize = pageScrollView.contentSize.forDirection(withConfiguration: self.readerConfig)
347 let contentOffset = pageScrollView.contentOffset.forDirection(withConfiguration: self.readerConfig)
348 self.pageOffsetRate = (contentSize != 0 ? (contentOffset / contentSize) : 0)
351 func setScrollDirection(_ direction: FolioReaderScrollDirection) {
352 guard let currentPage = self.currentPage, let webView = currentPage.webView else {
356 let pageScrollView = webView.scrollView
358 // Get internal page offset before layout change
359 self.updatePageOffsetRate()
361 self.readerConfig.scrollDirection = direction
362 self.collectionViewLayout.scrollDirection = .direction(withConfiguration: self.readerConfig)
363 self.currentPage?.setNeedsLayout()
364 self.collectionView.collectionViewLayout.invalidateLayout()
365 self.collectionView.setContentOffset(frameForPage(self.currentPageNumber).origin, animated: false)
367 // Page progressive direction
368 self.setCollectionViewProgressiveDirection()
369 delay(0.2) { self.setPageProgressiveDirection(currentPage) }
373 * This delay is needed because the page will not be ready yet
374 * so the delay wait until layout finished the changes.
377 var pageOffset = (pageScrollView.contentSize.forDirection(withConfiguration: self.readerConfig) * self.pageOffsetRate)
379 // Fix the offset for paged scroll
380 if (self.readerConfig.scrollDirection == .horizontal && self.pageWidth != 0) {
381 let page = round(pageOffset / self.pageWidth)
382 pageOffset = (page * self.pageWidth)
385 let pageOffsetPoint = self.readerConfig.isDirection(CGPoint(x: 0, y: pageOffset), CGPoint(x: pageOffset, y: 0), CGPoint(x: 0, y: pageOffset))
386 pageScrollView.setContentOffset(pageOffsetPoint, animated: true)
390 // MARK: Status bar and Navigation bar
393 guard self.readerConfig.shouldHideNavigationOnTap == true else {
397 self.updateBarsStatus(true)
401 self.configureNavBar()
402 self.updateBarsStatus(false)
406 guard self.readerConfig.shouldHideNavigationOnTap == true else {
410 let shouldHide = !self.navigationController!.isNavigationBarHidden
411 if shouldHide == false {
412 self.configureNavBar()
415 self.updateBarsStatus(shouldHide)
418 private func updateBarsStatus(_ shouldHide: Bool, shouldShowIndicator: Bool = false) {
419 guard let readerContainer = readerContainer else { return }
420 readerContainer.shouldHideStatusBar = shouldHide
422 UIView.animate(withDuration: 0.25, animations: {
423 readerContainer.setNeedsStatusBarAppearanceUpdate()
425 // Show minutes indicator
426 if (shouldShowIndicator == true) {
427 self.pageIndicatorView?.minutesLabel.alpha = shouldHide ? 0 : 1
430 self.navigationController?.setNavigationBarHidden(shouldHide, animated: true)
433 // MARK: UICollectionViewDataSource
435 open func numberOfSections(in collectionView: UICollectionView) -> Int {
439 open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
443 open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
444 let reuseableCell = collectionView.dequeueReusableCell(withReuseIdentifier: kReuseCellIdentifier, for: indexPath) as? FolioReaderPage
446 return self.configure(readerPageCell: reuseableCell, atIndexPath: indexPath)
449 private func configure(readerPageCell cell: FolioReaderPage?, atIndexPath indexPath: IndexPath) -> UICollectionViewCell {
450 guard let cell = cell, let readerContainer = readerContainer else {
451 return UICollectionViewCell()
454 var isNextChapter = true
455 if let lastRow = lastRow {
456 isNextChapter = lastRow < indexPath.row
461 lastRow = indexPath.row
463 cell.setup(withReaderContainer: readerContainer)
464 cell.pageNumber = indexPath.row+1
465 cell.webView?.scrollView.delegate = self
466 if #available(iOS 11.0, *) {
467 cell.webView?.scrollView.contentInsetAdjustmentBehavior = .never
469 cell.webView?.setupScrollDirection()
470 cell.webView?.frame = cell.webViewFrame()
472 cell.backgroundColor = .clear
474 setPageProgressiveDirection(cell)
476 // Configure the cell
477 let resource = self.book.spine.spineReferences[indexPath.row].resource
478 guard var html = try? String(contentsOfFile: resource.fullHref, encoding: String.Encoding.utf8) else {
482 let mediaOverlayStyleColors = "\"\(self.readerConfig.mediaOverlayColor.hexString(false))\", \"\(self.readerConfig.mediaOverlayColor.highlightColor().hexString(false))\""
485 let jsFilePath = Bundle.frameworkBundle().path(forResource: "Bridge", ofType: "js")
486 let cssFilePath = Bundle.frameworkBundle().path(forResource: "Style", ofType: "css")
487 let cssTag = "<link rel=\"stylesheet\" type=\"text/css\" href=\"\(cssFilePath!)\">"
488 let jsTag = "<script type=\"text/javascript\" src=\"\(jsFilePath!)\"></script>" +
489 "<script type=\"text/javascript\">setMediaOverlayStyleColors(\(mediaOverlayStyleColors))</script>"
491 let toInject = "\n\(cssTag)\n\(jsTag)\n</head>"
492 html = html.replacingOccurrences(of: "</head>", with: toInject)
495 var classes = folioReader.currentFont.cssIdentifier
496 classes += " " + folioReader.currentMediaOverlayStyle.className()
499 if folioReader.nightMode {
500 classes += " nightMode"
504 classes += " \(folioReader.currentFontSize.cssIdentifier(sliderType: .font))"
505 classes += " \(folioReader.currentMarginSize.cssIdentifier(sliderType: .margin))"
506 classes += " \(folioReader.currentInterlineSize.cssIdentifier(sliderType: .interline))"
510 html = html.replacingOccurrences(of: "<html ", with: "<html class=\"\(classes)\"")
512 // Let the delegate adjust the html string
513 if let modifiedHtmlContent = self.delegate?.htmlContentForPage?(cell, htmlContent: html) {
514 html = modifiedHtmlContent
517 // print("\n\nhtmlhtml\n\n\(html)")
519 cell.loadHTMLString(html, baseURL: URL(fileURLWithPath: resource.fullHref.deletingLastPathComponent), isNextChapter: isNextChapter)
523 public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
524 var size = CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
526 if #available(iOS 11.0, *) {
527 let orientation = UIDevice.current.orientation
529 if orientation == .portrait || orientation == .portraitUpsideDown {
530 if readerConfig.scrollDirection == .horizontal {
531 size.height = size.height - view.safeAreaInsets.bottom
539 // MARK: - Device rotation
541 override open func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
542 guard folioReader.isReaderReady else { return }
544 setPageSize(toInterfaceOrientation)
547 if self.currentOrientation == nil || (self.currentOrientation?.isPortrait != toInterfaceOrientation.isPortrait) {
548 var pageIndicatorFrame = pageIndicatorView?.frame
549 pageIndicatorFrame?.origin.y = ((screenBounds.size.height < screenBounds.size.width) ? (self.collectionView.frame.height - pageIndicatorHeight) : (self.collectionView.frame.width - pageIndicatorHeight))
550 pageIndicatorFrame?.origin.x = 0
551 pageIndicatorFrame?.size.width = ((screenBounds.size.height < screenBounds.size.width) ? (self.collectionView.frame.width) : (self.collectionView.frame.height))
552 pageIndicatorFrame?.size.height = pageIndicatorHeight
554 var scrollScrubberFrame = scrollScrubber?.slider.frame;
555 scrollScrubberFrame?.origin.x = ((screenBounds.size.height < screenBounds.size.width) ? (screenBounds.size.width - 100) : (screenBounds.size.height + 10))
556 scrollScrubberFrame?.size.height = ((screenBounds.size.height < screenBounds.size.width) ? (self.collectionView.frame.height - 100) : (self.collectionView.frame.width - 100))
558 self.collectionView.collectionViewLayout.invalidateLayout()
560 UIView.animate(withDuration: duration, animations: {
561 // Adjust page indicator view
562 if let pageIndicatorFrame = pageIndicatorFrame {
563 self.pageIndicatorView?.frame = pageIndicatorFrame
564 self.pageIndicatorView?.reloadView(updateShadow: true)
567 // Adjust scroll scrubber slider
568 if let scrollScrubberFrame = scrollScrubberFrame {
569 self.scrollScrubber?.slider.frame = scrollScrubberFrame
572 // Adjust collectionView
573 self.collectionView.contentSize = self.readerConfig.isDirection(
574 CGSize(width: self.pageWidth, height: self.pageHeight * CGFloat(self.totalPages)),
575 CGSize(width: self.pageWidth * CGFloat(self.totalPages), height: self.pageHeight),
576 CGSize(width: self.pageWidth * CGFloat(self.totalPages), height: self.pageHeight)
578 self.collectionView.setContentOffset(self.frameForPage(self.currentPageNumber).origin, animated: false)
579 self.collectionView.collectionViewLayout.invalidateLayout()
581 // Adjust internal page offset
582 self.updatePageOffsetRate()
586 self.currentOrientation = toInterfaceOrientation
589 override open func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
590 guard folioReader.isReaderReady == true, let currentPage = currentPage else {
595 pagesForCurrentPage(currentPage)
596 currentPage.refreshPageMode()
598 scrollScrubber?.setSliderVal()
600 // After rotation fix internal page offset
601 var pageOffset = (currentPage.webView?.scrollView.contentSize.forDirection(withConfiguration: self.readerConfig) ?? 0) * pageOffsetRate
603 // Fix the offset for paged scroll
604 if (self.readerConfig.scrollDirection == .horizontal && self.pageWidth != 0) {
605 let page = round(pageOffset / self.pageWidth)
606 pageOffset = page * self.pageWidth
609 let pageOffsetPoint = self.readerConfig.isDirection(CGPoint(x: 0, y: pageOffset), CGPoint(x: pageOffset, y: 0), CGPoint(x: 0, y: pageOffset))
610 currentPage.webView?.scrollView.setContentOffset(pageOffsetPoint, animated: true)
613 override open func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
614 guard folioReader.isReaderReady else {
618 self.collectionView.scrollToItem(at: IndexPath(row: self.currentPageNumber - 1, section: 0), at: UICollectionViewScrollPosition(), animated: false)
619 if (self.currentPageNumber + 1) >= totalPages {
620 UIView.animate(withDuration: duration, animations: {
621 self.collectionView.setContentOffset(self.frameForPage(self.currentPageNumber).origin, animated: false)
628 func setPageSize(_ orientation: UIInterfaceOrientation) {
629 guard orientation.isPortrait else {
630 if screenBounds.size.width > screenBounds.size.height {
631 self.pageWidth = screenBounds.size.width
632 self.pageHeight = screenBounds.size.height
634 self.pageWidth = screenBounds.size.height
635 self.pageHeight = screenBounds.size.width
640 if screenBounds.size.width < screenBounds.size.height {
641 self.pageWidth = screenBounds.size.width
642 self.pageHeight = screenBounds.size.height
644 self.pageWidth = screenBounds.size.height
645 self.pageHeight = screenBounds.size.width
649 func updateCurrentPage(_ page: FolioReaderPage? = nil, completion: (() -> Void)? = nil) {
652 self.previousPageNumber = page.pageNumber-1
653 self.currentPageNumber = page.pageNumber
655 let currentIndexPath = getCurrentIndexPath()
656 currentPage = collectionView.cellForItem(at: currentIndexPath) as? FolioReaderPage
658 self.previousPageNumber = currentIndexPath.row
659 self.currentPageNumber = currentIndexPath.row+1
662 self.nextPageNumber = (((self.currentPageNumber + 1) <= totalPages) ? (self.currentPageNumber + 1) : self.currentPageNumber)
665 guard let currentPage = currentPage else {
670 scrollScrubber?.setSliderVal()
672 if let readingTime = currentPage.webView?.js("getReadingTime()") {
673 pageIndicatorView?.totalMinutes = Int(readingTime)!
675 pageIndicatorView?.totalMinutes = 0
677 pagesForCurrentPage(currentPage)
679 delegate?.pageDidAppear?(currentPage)
680 delegate?.pageItemChanged?(self.getCurrentPageItemNumber())
685 func pagesForCurrentPage(_ page: FolioReaderPage?) {
686 guard let page = page, let webView = page.webView else { return }
688 let pageSize = self.readerConfig.isDirection(pageHeight, self.pageWidth, pageHeight)
689 let contentSize = page.webView?.scrollView.contentSize.forDirection(withConfiguration: self.readerConfig) ?? 0
690 self.pageIndicatorView?.totalPages = ((pageSize != 0) ? Int(ceil(contentSize / pageSize)) : 0)
692 let pageOffSet = self.readerConfig.isDirection(webView.scrollView.contentOffset.x, webView.scrollView.contentOffset.x, webView.scrollView.contentOffset.y)
693 let webViewPage = pageForOffset(pageOffSet, pageHeight: pageSize)
695 self.pageIndicatorView?.currentPage = webViewPage
698 func pageForOffset(_ offset: CGFloat, pageHeight height: CGFloat) -> Int {
699 guard (height != 0) else {
703 let page = Int(ceil(offset / height))+1
707 func getCurrentIndexPath() -> IndexPath {
708 let indexPaths = collectionView.indexPathsForVisibleItems
709 var indexPath = IndexPath()
711 if indexPaths.count > 1 {
712 let first = indexPaths.first!
713 let last = indexPaths.last!
715 switch self.pageScrollDirection {
717 if first.compare(last) == .orderedAscending {
723 if first.compare(last) == .orderedAscending {
730 indexPath = indexPaths.first ?? IndexPath(row: 0, section: 0)
736 func frameForPage(_ page: Int) -> CGRect {
737 return self.readerConfig.isDirection(
738 CGRect(x: 0, y: self.pageHeight * CGFloat(page-1), width: self.pageWidth, height: self.pageHeight),
739 CGRect(x: self.pageWidth * CGFloat(page-1), y: 0, width: self.pageWidth, height: self.pageHeight),
740 CGRect(x: 0, y: self.pageHeight * CGFloat(page-1), width: self.pageWidth, height: self.pageHeight)
744 open func changePageWith(page: Int, andFragment fragment: String, animated: Bool = false, completion: (() -> Void)? = nil) {
745 if (self.currentPageNumber == page) {
746 if let currentPage = currentPage , fragment != "" {
747 currentPage.handleAnchor(fragment, avoidBeginningAnchors: true, animated: animated)
751 tempFragment = fragment
752 changePageWith(page: page, animated: animated, completion: { () -> Void in
753 self.updateCurrentPage {
760 open func changePageWith(href: String, animated: Bool = false, completion: (() -> Void)? = nil) {
761 let item = findPageByHref(href)
762 let indexPath = IndexPath(row: item, section: 0)
763 changePageWith(indexPath: indexPath, animated: animated, completion: { () -> Void in
764 self.updateCurrentPage {
770 open func changePageWith(href: String, andAudioMarkID markID: String) {
771 if recentlyScrolled { return } // if user recently scrolled, do not change pages or scroll the webview
772 guard let currentPage = currentPage else { return }
774 let item = findPageByHref(href)
775 let pageUpdateNeeded = item+1 != currentPage.pageNumber
776 let indexPath = IndexPath(row: item, section: 0)
777 changePageWith(indexPath: indexPath, animated: true) { () -> Void in
778 if pageUpdateNeeded {
779 self.updateCurrentPage {
780 currentPage.audioMarkID(markID)
783 currentPage.audioMarkID(markID)
788 open func changePageWith(indexPath: IndexPath, animated: Bool = false, completion: (() -> Void)? = nil) {
789 guard indexPathIsValid(indexPath) else {
790 print("ERROR: Attempt to scroll to invalid index path")
795 UIView.animate(withDuration: animated ? 0.3 : 0, delay: 0, options: UIViewAnimationOptions(), animations: { () -> Void in
796 self.collectionView.scrollToItem(at: indexPath, at: .direction(withConfiguration: self.readerConfig), animated: false)
797 }) { (finished: Bool) -> Void in
802 open func changePageWith(href: String, pageItem: Int, animated: Bool = false, completion: (() -> Void)? = nil) {
803 changePageWith(href: href, animated: animated) {
804 self.changePageItem(to: pageItem)
808 func indexPathIsValid(_ indexPath: IndexPath) -> Bool {
809 let section = indexPath.section
810 let row = indexPath.row
811 let lastSectionIndex = numberOfSections(in: collectionView) - 1
813 //Make sure the specified section exists
814 if section > lastSectionIndex {
818 let rowCount = self.collectionView(collectionView, numberOfItemsInSection: indexPath.section) - 1
819 return row <= rowCount
822 open func isLastPage() -> Bool{
823 return (currentPageNumber == self.nextPageNumber)
826 public func changePageToNext(_ completion: (() -> Void)? = nil) {
827 changePageWith(page: self.nextPageNumber, animated: true) { () -> Void in
832 public func changePageToPrevious(_ completion: (() -> Void)? = nil) {
833 changePageWith(page: self.previousPageNumber, animated: true) { () -> Void in
838 public func changePageItemToNext(_ completion: (() -> Void)? = nil) {
839 // TODO: It was implemented for horizontal orientation.
840 // Need check page orientation (v/h) and make correct calc for vertical
842 let cell = collectionView.cellForItem(at: getCurrentIndexPath()) as? FolioReaderPage,
843 let contentOffset = cell.webView?.scrollView.contentOffset,
844 let contentOffsetXLimit = cell.webView?.scrollView.contentSize.width else {
849 let cellSize = cell.frame.size
850 let contentOffsetX = contentOffset.x + cellSize.width
852 if contentOffsetX >= contentOffsetXLimit {
853 changePageToNext(completion)
855 cell.scrollPageToOffset(contentOffsetX, animated: true)
861 public func getCurrentPageItemNumber() -> Int {
862 guard let page = currentPage, let webView = page.webView else { return 0 }
864 let pageSize = readerConfig.isDirection(pageHeight, pageWidth, pageHeight)
865 let pageOffSet = readerConfig.isDirection(webView.scrollView.contentOffset.x, webView.scrollView.contentOffset.x, webView.scrollView.contentOffset.y)
866 let webViewPage = pageForOffset(pageOffSet, pageHeight: pageSize)
871 public func changePageItemToPrevious(_ completion: (() -> Void)? = nil) {
872 // TODO: It was implemented for horizontal orientation.
873 // Need check page orientation (v/h) and make correct calc for vertical
875 let cell = collectionView.cellForItem(at: getCurrentIndexPath()) as? FolioReaderPage,
876 let contentOffset = cell.webView?.scrollView.contentOffset else {
881 let cellSize = cell.frame.size
882 let contentOffsetX = contentOffset.x - cellSize.width
884 if contentOffsetX < 0 {
885 changePageToPrevious(completion)
887 cell.scrollPageToOffset(contentOffsetX, animated: true)
893 public func changePageItemToLast(animated: Bool = true, _ completion: (() -> Void)? = nil) {
894 // TODO: It was implemented for horizontal orientation.
895 // Need check page orientation (v/h) and make correct calc for vertical
897 let cell = collectionView.cellForItem(at: getCurrentIndexPath()) as? FolioReaderPage,
898 let contentSize = cell.webView?.scrollView.contentSize else {
903 let cellSize = cell.frame.size
904 var contentOffsetX: CGFloat = 0.0
906 if contentSize.width > 0 && cellSize.width > 0 {
907 contentOffsetX = (cellSize.width * (contentSize.width / cellSize.width)) - cellSize.width
910 if contentOffsetX < 0 {
914 cell.scrollPageToOffset(contentOffsetX, animated: animated)
919 public func changePageItem(to: Int, animated: Bool = true, completion: (() -> Void)? = nil) {
920 // TODO: It was implemented for horizontal orientation.
921 // Need check page orientation (v/h) and make correct calc for vertical
923 let cell = collectionView.cellForItem(at: getCurrentIndexPath()) as? FolioReaderPage,
924 let contentSize = cell.webView?.scrollView.contentSize else {
925 delegate?.pageItemChanged?(getCurrentPageItemNumber())
930 let cellSize = cell.frame.size
931 var contentOffsetX: CGFloat = 0.0
933 if contentSize.width > 0 && cellSize.width > 0 {
934 contentOffsetX = (cellSize.width * CGFloat(to)) - cellSize.width
937 if contentOffsetX > contentSize.width {
938 contentOffsetX = contentSize.width - cellSize.width
941 if contentOffsetX < 0 {
945 UIView.animate(withDuration: animated ? 0.3 : 0, delay: 0, options: UIViewAnimationOptions(), animations: { () -> Void in
946 cell.scrollPageToOffset(contentOffsetX, animated: animated)
947 }) { (finished: Bool) -> Void in
948 self.updateCurrentPage {
955 Find a page by FRTocReference.
957 public func findPageByResource(_ reference: FRTocReference) -> Int {
959 for item in self.book.spine.spineReferences {
960 if let resource = reference.resource, item.resource == resource {
971 public func findPageByHref(_ href: String) -> Int {
973 for item in self.book.spine.spineReferences {
974 if item.resource.href == href {
983 Find and return the current chapter resource.
985 public func getCurrentChapter() -> FRResource? {
986 for item in self.book.flatTableOfContents {
988 let reference = self.book.spine.spineReferences[safe: (self.currentPageNumber - 1)],
989 let resource = item.resource,
990 (resource == reference.resource) {
998 Return the current chapter progress based on current chapter and total of chapters.
1000 public func getCurrentChapterProgress() -> CGFloat {
1001 let total = totalPages
1002 let current = currentPageNumber
1008 return CGFloat((100 * current) / total)
1012 Find and return the current chapter name.
1014 public func getCurrentChapterName() -> String? {
1015 for item in self.book.flatTableOfContents {
1017 let reference = self.book.spine.spineReferences[safe: (self.currentPageNumber - 1)],
1018 let resource = item.resource,
1019 (resource == reference.resource),
1020 let title = item.title else {
1030 // MARK: Public page methods
1033 Changes the current page of the reader.
1035 - parameter page: The target page index. Note: The page index starts at 1 (and not 0).
1036 - parameter animated: En-/Disables the animation of the page change.
1037 - parameter completion: A Closure which is called if the page change is completed.
1039 public func changePageWith(page: Int, animated: Bool = false, completion: (() -> Void)? = nil) {
1040 if page > 0 && page-1 < totalPages {
1041 let indexPath = IndexPath(row: page-1, section: 0)
1042 changePageWith(indexPath: indexPath, animated: animated, completion: { () -> Void in
1043 self.updateCurrentPage {
1050 // MARK: - Audio Playing
1052 func audioMark(href: String, fragmentID: String) {
1053 changePageWith(href: href, andAudioMarkID: fragmentID)
1059 Sharing chapter method.
1061 @objc func shareChapter(_ sender: UIBarButtonItem) {
1062 guard let currentPage = currentPage else { return }
1064 if let chapterText = currentPage.webView?.js("getBodyText()") {
1065 let htmlText = chapterText.replacingOccurrences(of: "[\\n\\r]+", with: "<br />", options: .regularExpression)
1066 var subject = readerConfig.localizedShareChapterSubject
1070 var chapterName = ""
1072 var shareItems = [AnyObject]()
1075 if let title = self.book.title {
1077 subject += " “\(title)”"
1081 if let chapter = getCurrentChapterName() {
1082 chapterName = chapter
1086 if let author = self.book.metadata.creators.first {
1087 authorName = author.name
1090 // Sharing html and text
1091 html = "<html><body>"
1092 html += "<br /><hr> <p>\(htmlText)</p> <hr><br />"
1093 html += "<center><p style=\"color:gray\">"+readerConfig.localizedShareAllExcerptsFrom+"</p>"
1094 html += "<b>\(bookTitle)</b><br />"
1095 html += readerConfig.localizedShareBy+" <i>\(authorName)</i><br />"
1097 if let bookShareLink = readerConfig.localizedShareWebLink {
1098 html += "<a href=\"\(bookShareLink.absoluteString)\">\(bookShareLink.absoluteString)</a>"
1099 shareItems.append(bookShareLink as AnyObject)
1102 html += "</center></body></html>"
1103 text = "\(chapterName)\n\n“\(chapterText)” \n\n\(bookTitle) \n\(readerConfig.localizedShareBy) \(authorName)"
1105 let act = FolioReaderSharingProvider(subject: subject, text: text, html: html)
1106 shareItems.insert(contentsOf: [act, "" as AnyObject], at: 0)
1108 let activityViewController = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)
1109 activityViewController.excludedActivityTypes = [UIActivityType.print, UIActivityType.postToVimeo]
1111 // Pop style on iPad
1112 if let actv = activityViewController.popoverPresentationController {
1113 actv.barButtonItem = sender
1116 present(activityViewController, animated: true, completion: nil)
1121 Sharing highlight method.
1123 func shareHighlight(_ string: String, rect: CGRect) {
1124 var subject = readerConfig.localizedShareHighlightSubject
1128 var chapterName = ""
1130 var shareItems = [AnyObject]()
1133 if let title = self.book.title {
1135 subject += " “\(title)”"
1139 if let chapter = getCurrentChapterName() {
1140 chapterName = chapter
1144 if let author = self.book.metadata.creators.first {
1145 authorName = author.name
1148 // Sharing html and text
1149 html = "<html><body>"
1150 html += "<br /><hr> <p>\(chapterName)</p>"
1151 html += "<p>\(string)</p> <hr><br />"
1152 html += "<center><p style=\"color:gray\">"+readerConfig.localizedShareAllExcerptsFrom+"</p>"
1153 html += "<b>\(bookTitle)</b><br />"
1154 html += readerConfig.localizedShareBy+" <i>\(authorName)</i><br />"
1156 if let bookShareLink = readerConfig.localizedShareWebLink {
1157 html += "<a href=\"\(bookShareLink.absoluteString)\">\(bookShareLink.absoluteString)</a>"
1158 shareItems.append(bookShareLink as AnyObject)
1161 html += "</center></body></html>"
1162 text = "\(chapterName)\n\n“\(string)” \n\n\(bookTitle) \n\(readerConfig.localizedShareBy) \(authorName)"
1164 let act = FolioReaderSharingProvider(subject: subject, text: text, html: html)
1165 shareItems.insert(contentsOf: [act, "" as AnyObject], at: 0)
1167 let activityViewController = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)
1168 activityViewController.excludedActivityTypes = [UIActivityType.print, UIActivityType.postToVimeo]
1170 // Pop style on iPad
1171 if let actv = activityViewController.popoverPresentationController {
1172 actv.sourceView = currentPage
1173 actv.sourceRect = rect
1176 present(activityViewController, animated: true, completion: nil)
1179 // MARK: - ScrollView Delegate
1181 open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
1182 self.isScrolling = true
1183 clearRecentlyScrolled()
1184 recentlyScrolled = true
1185 pointNow = scrollView.contentOffset
1187 if let currentPage = currentPage {
1188 currentPage.webView?.createMenu(options: true)
1189 currentPage.webView?.setMenuVisible(false)
1192 scrollScrubber?.scrollViewWillBeginDragging(scrollView)
1195 open func scrollViewDidScroll(_ scrollView: UIScrollView) {
1197 if (navigationController?.isNavigationBarHidden == false) {
1201 scrollScrubber?.scrollViewDidScroll(scrollView)
1203 let isCollectionScrollView = (scrollView is UICollectionView)
1204 let scrollType: ScrollType = ((isCollectionScrollView == true) ? .chapter : .page)
1206 // Update current reading page
1207 if (isCollectionScrollView == false), let page = currentPage, let webView = page.webView {
1209 let pageSize = self.readerConfig.isDirection(self.pageHeight, self.pageWidth, self.pageHeight)
1210 let contentOffset = webView.scrollView.contentOffset.forDirection(withConfiguration: self.readerConfig)
1211 let contentSize = webView.scrollView.contentSize.forDirection(withConfiguration: self.readerConfig)
1212 if (contentOffset + pageSize <= contentSize) {
1214 let webViewPage = pageForOffset(contentOffset, pageHeight: pageSize)
1216 if (readerConfig.scrollDirection == .horizontalWithVerticalContent) {
1217 let currentIndexPathRow = (page.pageNumber - 1)
1219 // if the cell reload doesn't save the top position offset
1220 if let oldOffSet = self.currentWebViewScrollPositions[currentIndexPathRow], (abs(oldOffSet.y - scrollView.contentOffset.y) > 100) {
1223 self.currentWebViewScrollPositions[currentIndexPathRow] = scrollView.contentOffset
1227 if (pageIndicatorView?.currentPage != webViewPage) {
1228 pageIndicatorView?.currentPage = webViewPage
1231 self.delegate?.pageItemChanged?(webViewPage)
1235 self.updatePageScrollDirection(inScrollView: scrollView, forScrollType: scrollType)
1238 private func updatePageScrollDirection(inScrollView scrollView: UIScrollView, forScrollType scrollType: ScrollType) {
1240 let scrollViewContentOffsetForDirection = scrollView.contentOffset.forDirection(withConfiguration: self.readerConfig, scrollType: scrollType)
1241 let pointNowForDirection = pointNow.forDirection(withConfiguration: self.readerConfig, scrollType: scrollType)
1242 // The movement is either positive or negative. This happens if the page change isn't completed. Toggle to the other scroll direction then.
1243 let isCurrentlyPositive = (self.pageScrollDirection == .left || self.pageScrollDirection == .up)
1245 if (scrollViewContentOffsetForDirection < pointNowForDirection) {
1246 self.pageScrollDirection = .negative(withConfiguration: self.readerConfig, scrollType: scrollType)
1247 } else if (scrollViewContentOffsetForDirection > pointNowForDirection) {
1248 self.pageScrollDirection = .positive(withConfiguration: self.readerConfig, scrollType: scrollType)
1249 } else if (isCurrentlyPositive == true) {
1250 self.pageScrollDirection = .negative(withConfiguration: self.readerConfig, scrollType: scrollType)
1252 self.pageScrollDirection = .positive(withConfiguration: self.readerConfig, scrollType: scrollType)
1256 open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
1257 self.isScrolling = false
1259 // Perform the page after a short delay as the collection view hasn't completed it's transition if this method is called (the index paths aren't right during fast scrolls).
1260 delay(0.2, closure: { [weak self] in
1261 if (self?.readerConfig.scrollDirection == .horizontalWithVerticalContent),
1262 let cell = ((scrollView.superview as? UIWebView)?.delegate as? FolioReaderPage) {
1263 let currentIndexPathRow = cell.pageNumber - 1
1264 self?.currentWebViewScrollPositions[currentIndexPathRow] = scrollView.contentOffset
1267 if (scrollView is UICollectionView) {
1268 guard let instance = self else {
1272 if instance.totalPages > 0 {
1273 instance.updateCurrentPage()
1274 instance.delegate?.pageItemChanged?(instance.getCurrentPageItemNumber())
1277 self?.scrollScrubber?.scrollViewDidEndDecelerating(scrollView)
1282 open func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
1283 if scrollView == collectionView{
1284 scrollView.isUserInteractionEnabled = false
1285 delay(0.5, closure: { [weak self] in
1286 scrollView.isUserInteractionEnabled = true
1290 recentlyScrolledTimer = Timer(timeInterval:recentlyScrolledDelay, target: self, selector: #selector(FolioReaderCenter.clearRecentlyScrolled), userInfo: nil, repeats: false)
1291 RunLoop.current.add(recentlyScrolledTimer, forMode: RunLoopMode.commonModes)
1294 @objc func clearRecentlyScrolled() {
1295 if(recentlyScrolledTimer != nil) {
1296 recentlyScrolledTimer.invalidate()
1297 recentlyScrolledTimer = nil
1299 recentlyScrolled = false
1302 open func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
1303 scrollScrubber?.scrollViewDidEndScrollingAnimation(scrollView)
1306 // MARK: NavigationBar Actions
1308 @objc func closeReader(_ sender: UIBarButtonItem) {
1315 Present chapter list
1317 @objc func presentChapterList(_ sender: UIBarButtonItem) {
1318 folioReader.saveReaderState()
1320 let chapter = FolioReaderChapterList(folioReader: folioReader, readerConfig: readerConfig, book: book, delegate: self)
1321 chapter.title = "Spis treści"
1322 // let highlight = FolioReaderHighlightList(folioReader: folioReader, readerConfig: readerConfig)
1323 // let pageController = PageViewController(folioReader: folioReader, readerConfig: readerConfig)
1325 // pageController.viewControllerOne = chapter
1326 // pageController.viewControllerTwo = highlight
1327 // pageController.segmentedControlItems = [readerConfig.localizedContentsTitle, readerConfig.localizedHighlightsTitle]
1329 let vc = FolioReaderModalViewController(folioReader: folioReader, readerConfig: readerConfig)
1331 vc.viewControllerOne = chapter
1332 let nav = UINavigationController(rootViewController:vc /*pageController*/)
1333 present(nav, animated: true, completion: nil)
1337 Present fonts and settings menu
1339 @objc func presentFontsMenu() {
1340 folioReader.saveReaderState()
1343 let menu = FolioReaderFontsMenu(folioReader: folioReader, readerConfig: readerConfig)
1344 menu.modalPresentationStyle = .custom
1346 animator = ZFModalTransitionAnimator(modalViewController: menu)
1347 animator.isDragable = false
1348 animator.bounces = false
1349 animator.behindViewAlpha = 0.4
1350 animator.behindViewScale = 1
1351 animator.transitionDuration = 0.6
1352 animator.direction = ZFModalTransitonDirection.bottom
1354 menu.transitioningDelegate = animator
1355 self.present(menu, animated: true, completion: nil)
1359 Present audio player menu
1361 @objc func presentPlayerMenu(_ sender: UIBarButtonItem) {
1362 folioReader.saveReaderState()
1365 let menu = FolioReaderPlayerMenu(folioReader: folioReader, readerConfig: readerConfig)
1366 menu.modalPresentationStyle = .custom
1368 animator = ZFModalTransitionAnimator(modalViewController: menu)
1369 animator.isDragable = true
1370 animator.bounces = false
1371 animator.behindViewAlpha = 0.4
1372 animator.behindViewScale = 1
1373 animator.transitionDuration = 0.6
1374 animator.direction = ZFModalTransitonDirection.bottom
1376 menu.transitioningDelegate = animator
1377 present(menu, animated: true, completion: nil)
1383 func presentQuoteShare(_ string: String) {
1384 let quoteShare = FolioReaderQuoteShare(initWithText: string, readerConfig: readerConfig, folioReader: folioReader, book: book)
1385 let nav = UINavigationController(rootViewController: quoteShare)
1387 if UIDevice.current.userInterfaceIdiom == .pad {
1388 nav.modalPresentationStyle = .formSheet
1390 present(nav, animated: true, completion: nil)
1394 // MARK: FolioPageDelegate
1396 extension FolioReaderCenter: FolioReaderPageDelegate {
1398 public func pageDidLoad(_ page: FolioReaderPage) {
1399 if self.readerConfig.loadSavedPositionForCurrentBook, let position = folioReader.savedPositionForCurrentBook {
1400 let pageNumber = position["pageNumber"] as? Int
1401 let offset = self.readerConfig.isDirection(position["pageOffsetY"], position["pageOffsetX"], position["pageOffsetY"]) as? CGFloat
1402 let pageOffset = offset
1405 updateCurrentPage(page)
1408 if (self.currentPageNumber == pageNumber && pageOffset > 0) {
1409 page.scrollPageToOffset(pageOffset!, animated: false)
1411 } else if (self.isScrolling == false && folioReader.needsRTLChange == true) {
1412 page.scrollPageToBottom()
1414 } else if isFirstLoad {
1415 updateCurrentPage(page)
1419 // Go to fragment if needed
1420 if let fragmentID = tempFragment, let currentPage = currentPage , fragmentID != "" {
1421 currentPage.handleAnchor(fragmentID, avoidBeginningAnchors: true, animated: true)
1425 if (readerConfig.scrollDirection == .horizontalWithVerticalContent),
1426 let offsetPoint = self.currentWebViewScrollPositions[page.pageNumber - 1] {
1427 page.webView?.scrollView.setContentOffset(offsetPoint, animated: false)
1430 // Pass the event to the centers `pageDelegate`
1431 pageDelegate?.pageDidLoad?(page)
1434 public func pageWillLoad(_ page: FolioReaderPage) {
1435 // Pass the event to the centers `pageDelegate`
1436 pageDelegate?.pageWillLoad?(page)
1439 public func pageTap(_ recognizer: UITapGestureRecognizer) {
1440 // Pass the event to the centers `pageDelegate`
1441 pageDelegate?.pageTap?(recognizer)
1446 // MARK: FolioReaderChapterListDelegate
1448 extension FolioReaderCenter: FolioReaderChapterListDelegate {
1450 func chapterList(_ chapterList: FolioReaderChapterList, didSelectRowAtIndexPath indexPath: IndexPath, withTocReference reference: FRTocReference) {
1451 let item = findPageByResource(reference)
1453 if item < totalPages {
1454 let indexPath = IndexPath(row: item, section: 0)
1455 changingChapter = true
1456 changePageWith(indexPath: indexPath, animated: false, completion: { () -> Void in
1457 self.updateCurrentPage()
1458 self.changingChapter = false
1460 tempReference = reference
1462 print("Failed to load book because the requested resource is missing.")
1466 func chapterList(didDismissedChapterList chapterList: FolioReaderChapterList) {
1469 // Move to #fragment
1470 if let reference = tempReference {
1471 if let fragmentID = reference.fragmentID, let currentPage = currentPage , fragmentID != "" {
1472 currentPage.handleAnchor(reference.fragmentID!, avoidBeginningAnchors: true, animated: true)
1478 func getScreenBounds() -> CGRect {
1479 var bounds = view.frame
1481 if #available(iOS 11.0, *) {
1482 bounds.size.height = bounds.size.height - view.safeAreaInsets.bottom
1490 class FolioReaderModalViewController: UIViewController{
1492 var viewControllerOne: UIViewController!
1493 fileprivate var readerConfig: FolioReaderConfig
1494 fileprivate var folioReader: FolioReader
1498 init(folioReader: FolioReader, readerConfig: FolioReaderConfig) {
1499 self.folioReader = folioReader
1500 self.readerConfig = readerConfig
1501 super.init(nibName: nil, bundle: nil)
1502 self.edgesForExtendedLayout = UIRectEdge()
1503 self.extendedLayoutIncludesOpaqueBars = true
1506 required init?(coder: NSCoder) {
1507 fatalError("storyboards are incompatible with truth and beauty")
1510 override func viewDidLoad() {
1513 self.navigationItem.title = viewControllerOne.title
1515 addChildViewController(viewControllerOne)
1516 viewControllerOne.view.frame = view.bounds
1517 view.addSubview(viewControllerOne.view)
1518 viewControllerOne.view.translatesAutoresizingMaskIntoConstraints = false
1519 if #available(iOS 9.0, *) {
1520 viewControllerOne.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
1521 viewControllerOne.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
1522 viewControllerOne.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
1523 viewControllerOne.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
1525 // Fallback on earlier versions
1527 viewControllerOne.didMove(toParentViewController: self)
1529 // self.view.backgroundColor = UIColor.white
1530 // self.setViewControllers([viewList[index]], direction: .forward, animated: false, completion: nil)
1533 let greenColor = UIColor(red:0.00, green:0.51, blue:0.53, alpha:1.00)
1534 let tintColor = folioReader.isNight(greenColor, UIColor.white)
1536 let closeImage = UIImage(readerImageNamed: "icon-navbar-close")?.imageTintColor(tintColor)?.withRenderingMode(.alwaysOriginal)// ignoreSystemTint(withConfiguration: readerConfig)
1537 self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: closeImage, style: .plain, target: self, action: #selector(dismiss as () -> Void))
1540 override func viewWillAppear(_ animated: Bool) {
1541 super.viewWillAppear(animated)
1545 func configureNavBar() {
1547 let greenColor = UIColor(red:0.00, green:0.51, blue:0.53, alpha:1.00)
1548 let navBackground = folioReader.isNight(self.readerConfig.nightModeMenuBackground, greenColor)
1549 let tintColor = folioReader.isNight(greenColor, UIColor.white)
1550 let navText = tintColor
1551 let font = UIFont(name: "Avenir-Light", size: 17)!
1552 setTranslucentNavigation(color: navBackground, tintColor: tintColor, titleColor: navText, andFont: font)
1556 override var preferredStatusBarStyle : UIStatusBarStyle {
1557 return .lightContent //self.folioReader.isNight(.lightContent, .default)