feat: improve epub reader controls and annotations

This commit is contained in:
shen
2026-07-12 12:23:10 +08:00
parent 063a493b18
commit 8ccb7157b2
36 changed files with 2959 additions and 2160 deletions
@@ -527,10 +527,13 @@ public class RDEpubReaderView: UIView {
let pageView = preloadController.pageViewForDisplay(
pageNum: pageNum,
environment: preloadEnvironment,
contentViewProvider: pageContentViewForDisplay(pageNum:reusableView:)
contentViewProvider: contentViewForPage(_:reusableView:)
)
// A data-source request is only a candidate. UIPageViewController may
// ask for duplicate instances of the current page; activation happens
// after the winning controller is actually installed.
(pageView as? RDEpubReaderPageVisibilityHandling)?
.readerPageVisibilityDidChange(isVisible: isReaderPageVisible(pageNum))
.readerPageVisibilityDidChange(isVisible: false)
return pageView
}
@@ -551,10 +554,6 @@ public class RDEpubReaderView: UIView {
return contentViewForPage(pageNum, reusableView: reusableView)
}
private func pageContentViewForDisplay(pageNum: Int, reusableView: UIView?) -> UIView? {
return contentViewForPage(pageNum, reusableView: reusableView)
}
func isReaderPageVisible(_ pageNum: Int) -> Bool {
guard currentPage >= 0 else { return pageNum == 0 }
if landscapeDualPageEnabled && isLandscape {
@@ -564,7 +563,7 @@ public class RDEpubReaderView: UIView {
return pageNum == currentPage
}
private func updatePageContentVisibility() {
func updatePageContentVisibility() {
var visiblePageNumbers = Set<Int>()
if currentPage >= 0 {
visiblePageNumbers.insert(currentPage)
@@ -574,20 +573,57 @@ public class RDEpubReaderView: UIView {
}
}
preloadController.updatePageVisibility(visiblePageNumbers: visiblePageNumbers)
for viewController in pageViewController.viewControllers ?? [] {
guard let pageController = viewController as? RDEpubReaderPageChildViewController,
let contentView = pageController.contentView else { continue }
(contentView as? RDEpubReaderPageVisibilityHandling)?
.readerPageVisibilityDidChange(isVisible: visiblePageNumbers.contains(pageController.pageNum))
let pageControllers: [RDEpubReaderPageChildViewController]
let visibleCellContents: [(pageNumber: Int, contentView: UIView)]
if currentDisplayType == .pageCurl {
pageControllers = (pageViewController.viewControllers ?? []).compactMap {
$0 as? RDEpubReaderPageChildViewController
}
visibleCellContents = []
} else {
pageControllers = []
visibleCellContents = collectionView.indexPathsForVisibleItems.compactMap { indexPath in
guard let cell = collectionView.cellForItem(at: indexPath) as? RDEpubReaderContentCell,
let contentView = cell.containerView else { return nil }
return (indexPath.item, contentView)
}
}
for indexPath in collectionView.indexPathsForVisibleItems {
guard let cell = collectionView.cellForItem(at: indexPath) as? RDEpubReaderContentCell,
let contentView = cell.containerView else { continue }
for pageController in pageControllers where !visiblePageNumbers.contains(pageController.pageNum) {
guard let contentView = pageController.contentView else { continue }
(contentView as? RDEpubReaderPageVisibilityHandling)?
.readerPageVisibilityDidChange(isVisible: visiblePageNumbers.contains(indexPath.item))
.readerPageVisibilityDidChange(isVisible: false)
}
for entry in visibleCellContents where !visiblePageNumbers.contains(entry.pageNumber) {
(entry.contentView as? RDEpubReaderPageVisibilityHandling)?
.readerPageVisibilityDidChange(isVisible: false)
}
// Suspend every cached non-winner before the attached winner below is
// activated. Object identity matters because a page number can have
// multiple UIPageViewController candidates.
let visibleViewIdentifiers = Set(
pageControllers.compactMap { pageController -> ObjectIdentifier? in
guard visiblePageNumbers.contains(pageController.pageNum),
let contentView = pageController.contentView else { return nil }
return ObjectIdentifier(contentView)
} + visibleCellContents.compactMap { entry -> ObjectIdentifier? in
guard visiblePageNumbers.contains(entry.pageNumber) else { return nil }
return ObjectIdentifier(entry.contentView)
}
)
preloadController.updatePageVisibility(visibleViewIdentifiers: visibleViewIdentifiers)
for pageController in pageControllers where visiblePageNumbers.contains(pageController.pageNum) {
guard let contentView = pageController.contentView else { continue }
(contentView as? RDEpubReaderPageVisibilityHandling)?
.readerPageVisibilityDidChange(isVisible: true)
}
for entry in visibleCellContents where visiblePageNumbers.contains(entry.pageNumber) {
(entry.contentView as? RDEpubReaderPageVisibilityHandling)?
.readerPageVisibilityDidChange(isVisible: true)
}
}
@@ -629,6 +665,11 @@ public class RDEpubReaderView: UIView {
}
private func detachPageViewControllerIfNeeded() {
for case let childController as RDEpubReaderPageChildViewController in pageViewController.viewControllers ?? [] {
guard let contentView = childController.contentView else { continue }
(contentView as? RDEpubReaderPageVisibilityHandling)?
.readerPageVisibilityDidChange(isVisible: false)
}
if pageViewController.parent != nil {
pageViewController.willMove(toParent: nil)
}
@@ -882,6 +923,17 @@ public class RDEpubReaderView: UIView {
}
predictedPageDirection = currentPage >= 0 ? safePageNum >= currentPage : nil
attachPageViewControllerIfNeeded()
// Programmatic setViewControllers transitions do not call the
// UIPageViewControllerDelegate didFinishAnimating callback. Stop
// the outgoing controllers explicitly before replacing them, even
// when a duplicate page instance has fallen out of our cache map.
for case let childController as RDEpubReaderPageChildViewController in pageViewController.viewControllers ?? [] {
guard let contentView = childController.contentView else { continue }
(contentView as? RDEpubReaderPageVisibilityHandling)?
.readerPageVisibilityDidChange(isVisible: false)
}
if isDualPage {
let pair = dualPagePair(for: safePageNum)
let leftContent = pageViewForDisplay(pageNum: pair.left)
@@ -903,7 +955,11 @@ public class RDEpubReaderView: UIView {
self.finishPageCurlTransition()
}
}
let pageChanged = currentPage != pair.left
currentPage = pair.left
if !pageChanged {
updatePageContentVisibility()
}
primePageCache(around: pair.left, preferredForward: predictedPageDirection)
} else {
let contentView = pageViewForDisplay(pageNum: safePageNum)
@@ -913,7 +969,11 @@ public class RDEpubReaderView: UIView {
guard let self else { return }
self.finishPageCurlTransition()
}
let pageChanged = currentPage != safePageNum
currentPage = safePageNum
if !pageChanged {
updatePageContentVisibility()
}
primePageCache(around: safePageNum, preferredForward: predictedPageDirection)
}
default:
@@ -921,7 +981,11 @@ public class RDEpubReaderView: UIView {
collectionView.layoutIfNeeded()
predictedPageDirection = currentPage >= 0 ? safePageNum >= currentPage : nil
collectionView.setContentOffset(layout.currentContentOffset(count: safePageNum), animated: animated)
let pageChanged = currentPage != safePageNum
currentPage = safePageNum
if !pageChanged {
updatePageContentVisibility()
}
primePageCache(around: safePageNum, preferredForward: predictedPageDirection)
}
}