import UIKit extension RDEpubReaderView: UIPageViewControllerDataSource, UIPageViewControllerDelegate { private func makeSinglePageChildVC(for pageNum: Int) -> RDEpubReaderPageChildViewController { if pageNum == RDEpubReaderView.blankPageNum || pageNum == RDEpubReaderView.blankEndPageNum { return RDEpubReaderPageChildViewController(contentView: UIView(), pageNum: pageNum) } let contentView = pageViewForDisplay(pageNum: pageNum) return RDEpubReaderPageChildViewController(contentView: contentView, pageNum: pageNum) } private func nextPageNum(after pageNum: Int, isDualPage: Bool) -> Int? { let totalPages = numberOfPages() if pageNum == RDEpubReaderView.blankEndPageNum { return nil } if isDualPage, let coverIndex = coverPageIndex { if pageNum == coverIndex { return RDEpubReaderView.blankPageNum } if pageNum == RDEpubReaderView.blankPageNum { let firstContent = coverIndex + 1 return firstContent < totalPages ? firstContent : nil } } let next = pageNum + 1 if next < totalPages { return next } if isDualPage { if let coverIndex = coverPageIndex { let adjustedIndex = pageNum - (coverIndex + 1) if adjustedIndex >= 0 && adjustedIndex % 2 == 0 { return RDEpubReaderView.blankEndPageNum } } else { if pageNum % 2 == 0 { return RDEpubReaderView.blankEndPageNum } } } return nil } private func prevPageNum(before pageNum: Int, isDualPage: Bool) -> Int? { if pageNum == RDEpubReaderView.blankEndPageNum { let totalPages = numberOfPages() return totalPages > 0 ? totalPages - 1 : nil } if isDualPage, let coverIndex = coverPageIndex { if pageNum == RDEpubReaderView.blankPageNum { return coverIndex } if pageNum == coverIndex + 1 { return RDEpubReaderView.blankPageNum } } let prev = pageNum - 1 return prev >= 0 ? prev : nil } public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let vc = viewController as? RDEpubReaderPageChildViewController else { return nil } let isDualPage = landscapeDualPageEnabled && isLandscape let isRTL = pageDirection == .rightToLeft let targetNum = isRTL ? nextPageNum(after: vc.pageNum, isDualPage: isDualPage) : prevPageNum(before: vc.pageNum, isDualPage: isDualPage) guard let num = targetNum else { return nil } let targetVC = makeSinglePageChildVC(for: num) willPreviousTransitionToViewController = targetVC return targetVC } public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let vc = viewController as? RDEpubReaderPageChildViewController else { return nil } let isDualPage = landscapeDualPageEnabled && isLandscape let isRTL = pageDirection == .rightToLeft let targetNum = isRTL ? prevPageNum(before: vc.pageNum, isDualPage: isDualPage) : nextPageNum(after: vc.pageNum, isDualPage: isDualPage) guard let num = targetNum else { return nil } let targetVC = makeSinglePageChildVC(for: num) willNextTransitionToViewController = targetVC return targetVC } public func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { let visibleControllers = pageViewController.viewControllers ?? [] let visibleControllerIdentifiers = Set(visibleControllers.map(ObjectIdentifier.init)) let transitionControllers = previousViewControllers + [ willPreviousTransitionToViewController, willNextTransitionToViewController, willTransitionToViewController ].compactMap { $0 } var hiddenContentViews = Set() // UIPageViewController can ask its data source for a second instance of // the same page and leave the first one outside our cache dictionaries. // Explicitly stop every controller that did not win the transition so // an orphaned Hype WebView cannot keep narrating in the background. for controller in transitionControllers where !visibleControllerIdentifiers.contains(ObjectIdentifier(controller)) { guard let childController = controller as? RDEpubReaderPageChildViewController, let contentView = childController.contentView else { continue } let identifier = ObjectIdentifier(contentView) guard hiddenContentViews.insert(identifier).inserted else { continue } (contentView as? RDEpubReaderPageVisibilityHandling)? .readerPageVisibilityDidChange(isVisible: false) } if completed, let firstVC = pageViewController.viewControllers?.first as? RDEpubReaderPageChildViewController { let pn = firstVC.pageNum if pn != RDEpubReaderView.blankPageNum && pn != RDEpubReaderView.blankEndPageNum { currentPage = pn primePageCache(around: pn, preferredForward: predictedPageDirection) } } // On cancellation currentPage does not change, so its didSet cannot // restore visibility. Reassert the actual controllers in both paths. for case let childController as RDEpubReaderPageChildViewController in visibleControllers { guard let contentView = childController.contentView else { continue } (contentView as? RDEpubReaderPageVisibilityHandling)? .readerPageVisibilityDidChange(isVisible: isReaderPageVisible(childController.pageNum)) } predictedPageDirection = nil finishPageCurlTransition() } public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { pagingController.isTransitioning = true willTransitionToViewController = pendingViewControllers.first if let target = pendingViewControllers.first as? RDEpubReaderPageChildViewController, target.pageNum != RDEpubReaderView.blankPageNum, target.pageNum != RDEpubReaderView.blankEndPageNum { predictedPageDirection = target.pageNum >= currentPage primePageCache(around: target.pageNum, preferredForward: predictedPageDirection) } else { } } }