ReadViewSDK/Sources/RDEpubReaderView/ReaderView/RDEpubReaderView+PageCurl.swift

153 lines
6.9 KiB
Swift

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<ObjectIdentifier>()
// 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 {
}
}
}