feat: EPUB阅读器搜索、注释、CFI模块及大书远距跳转优化
- 实现EPUB阅读器搜索功能及选中注释功能 - 优化CFI模块,修复代码审查发现的11个问题 - 实现大书远距目录跳转与后台补全优化方案 - 优化设置面板与章节运行时联动 - 重构及大量改进优化
This commit is contained in:
@@ -1,35 +1,34 @@
|
||||
// RDEPUBReaderController+DataSource.swift
|
||||
// EPUB 阅读器数据源与代理实现
|
||||
// 实现 RDReaderDataSource 和 RDReaderDelegate 协议,为阅读器视图提供页面数量、
|
||||
// 页面内容视图、页码变化通知、屏幕方向变化处理等核心数据与事件支持。
|
||||
|
||||
import UIKit
|
||||
|
||||
/// RDEPUBReaderController 数据源与代理扩展
|
||||
///
|
||||
/// 本文件实现 RDReaderDataSource 和 RDReaderDelegate 协议,为阅读器视图提供页面数量、
|
||||
/// 页面内容视图、页码变化通知、屏幕方向变化处理等核心数据与事件支持。
|
||||
|
||||
// MARK: - RDReaderView 数据源与代理
|
||||
|
||||
extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
/// 返回阅读器总页数
|
||||
|
||||
public func pageCountOfReaderView(readerView: RDReaderView) -> Int {
|
||||
readerContext.bookPageMap?.totalPages ?? textBook?.pages.count ?? activePages.count
|
||||
}
|
||||
|
||||
/// 为指定页码创建或复用内容视图(优先文本渲染,回退 Web 渲染)
|
||||
public func pageContentView(readerView: RDReaderView, pageNum: Int, containerView: UIView?) -> UIView {
|
||||
if readerContext.bookPageMap != nil {
|
||||
_ = runtime.prepareOnDemandChapter(forAbsolutePageNumber: pageNum + 1)
|
||||
if let resolvedPage = runtime.pageResolver.resolvePage(absolutePageIndex: pageNum) {
|
||||
let contentView = (containerView as? RDEPUBTextContentView) ?? RDEPUBTextContentView()
|
||||
contentView.delegate = self
|
||||
readerView.registerSelectionGestureDependenciesIfNeeded(for: contentView)
|
||||
contentView.selectionTapSuppressionDidChange = { [weak readerView, weak contentView] isSuppressed in
|
||||
guard let readerView, let contentView else { return }
|
||||
readerView.updateSelectionTapSuppression(for: contentView, isSuppressed: isSuppressed)
|
||||
}
|
||||
contentView.selectionPagingSuppressionDidChange = { [weak readerView, weak contentView] isSuppressed in
|
||||
guard let readerView, let contentView else { return }
|
||||
readerView.updateSelectionPagingSuppression(for: contentView, isSuppressed: isSuppressed)
|
||||
}
|
||||
contentView.configure(
|
||||
page: resolvedPage.page,
|
||||
pageNumber: pageNum + 1,
|
||||
totalPages: pageCountOfReaderView(readerView: readerView),
|
||||
configuration: configuration,
|
||||
chapterCFIMap: resolvedPage.chapter.chapterOffsetMap.cfiMap,
|
||||
chapterFragmentOffsets: resolvedPage.chapter.chapterOffsetMap.fragmentOffsets,
|
||||
highlights: textHighlights(for: resolvedPage.page),
|
||||
searchState: searchState(for: resolvedPage.page)
|
||||
)
|
||||
@@ -40,11 +39,22 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
if let textBook, let page = textBook.page(at: pageNum + 1) {
|
||||
let contentView = (containerView as? RDEPUBTextContentView) ?? RDEPUBTextContentView()
|
||||
contentView.delegate = self
|
||||
readerView.registerSelectionGestureDependenciesIfNeeded(for: contentView)
|
||||
contentView.selectionTapSuppressionDidChange = { [weak readerView, weak contentView] isSuppressed in
|
||||
guard let readerView, let contentView else { return }
|
||||
readerView.updateSelectionTapSuppression(for: contentView, isSuppressed: isSuppressed)
|
||||
}
|
||||
contentView.selectionPagingSuppressionDidChange = { [weak readerView, weak contentView] isSuppressed in
|
||||
guard let readerView, let contentView else { return }
|
||||
readerView.updateSelectionPagingSuppression(for: contentView, isSuppressed: isSuppressed)
|
||||
}
|
||||
contentView.configure(
|
||||
page: page,
|
||||
pageNumber: pageNum + 1,
|
||||
totalPages: textBook.pages.count,
|
||||
configuration: configuration,
|
||||
chapterCFIMap: textBook.chapterData(for: page.href)?.chapter.cfiMap,
|
||||
chapterFragmentOffsets: textBook.chapterData(for: page.href)?.chapter.fragmentOffsets ?? [:],
|
||||
highlights: textHighlights(for: page),
|
||||
searchState: searchState(for: page)
|
||||
)
|
||||
@@ -68,14 +78,12 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
return contentView
|
||||
}
|
||||
|
||||
/// 返回页面内容视图的重用标识符(区分文本与 Web 渲染)
|
||||
public func pageIdentifier(readerView: RDReaderView, pageNum: Int) -> String? {
|
||||
(textBook == nil && readerContext.bookPageMap == nil)
|
||||
? NSStringFromClass(RDEPUBWebContentView.self)
|
||||
: NSStringFromClass(RDEPUBTextContentView.self)
|
||||
}
|
||||
|
||||
/// 获取指定文本页面上的高亮标注列表
|
||||
private func textHighlights(for page: RDEPUBTextPage) -> [RDEPUBHighlight] {
|
||||
if let textBook,
|
||||
let chapterData = textBook.chapterData(for: page.href) {
|
||||
@@ -91,9 +99,6 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// 返回当前文本页需要展示的搜索状态。
|
||||
/// 这里会先按页过滤命中结果,并把 currentMatchIndex 重映射到当前页内的局部索引,
|
||||
/// 这样渲染层不需要再关心 href 规范化差异。
|
||||
private func searchState(for page: RDEPUBTextPage) -> RDEPUBSearchState? {
|
||||
guard let globalSearchState = searchState else { return nil }
|
||||
|
||||
@@ -237,6 +242,7 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
title: runtimeChapter.title,
|
||||
attributedContent: runtimeChapter.typesetAttributedString,
|
||||
fragmentOffsets: runtimeChapter.chapterOffsetMap.fragmentOffsets,
|
||||
cfiMap: runtimeChapter.chapterOffsetMap.cfiMap,
|
||||
pageBreakReasons: runtimeChapter.pages.map(\.metadata.breakReason),
|
||||
pages: runtimeChapter.pages
|
||||
)
|
||||
@@ -274,31 +280,25 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
return publication.resourceResolver.normalizedHref(href) ?? href
|
||||
}
|
||||
|
||||
/// 根据规范化 href 获取文本章节数据
|
||||
func textChapterData(forNormalizedHref href: String) -> RDEPUBChapterData? {
|
||||
readerContext.textChapterData(forNormalizedHref: href)
|
||||
}
|
||||
|
||||
/// 返回阅读器顶部工具视图
|
||||
public func topToolView(readerView: RDReaderView) -> UIView? {
|
||||
topToolView
|
||||
}
|
||||
|
||||
/// 返回阅读器底部工具视图
|
||||
public func bottomToolView(readerView: RDReaderView) -> UIView? {
|
||||
bottomToolView
|
||||
}
|
||||
|
||||
/// 页码变化回调:清除选区、同步阅读状态、检测到达末尾
|
||||
public func pageNum(readerView: RDReaderView, pageNum: Int) {
|
||||
readerContext.markUserNavigationActivity()
|
||||
updateCurrentSelection(nil)
|
||||
reconcileTextPaginationSizeIfNeeded(for: pageNum)
|
||||
|
||||
// 如果正在重新分页,跳过后续操作,避免状态冲突
|
||||
guard !isRepaginating else { return }
|
||||
|
||||
// 用户开始导航时,应用后台解析完成的完整 map
|
||||
let previousCurrentPage = readerView.currentPage
|
||||
runtime.applyPendingFullPageMapIfNeeded()
|
||||
let effectivePageNum = readerView.currentPage >= 0 ? readerView.currentPage : pageNum
|
||||
@@ -320,7 +320,7 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
let location = resolvedTextLocation(forPageNumber: effectivePageNum + 1) {
|
||||
persist(location: location)
|
||||
synchronizeTextReadingState(pageNumber: effectivePageNum + 1, location: location)
|
||||
// 记录翻页到 JumpSession
|
||||
|
||||
runtime.locationCoordinator.recordPageChangeIfNeeded()
|
||||
return
|
||||
}
|
||||
@@ -332,17 +332,14 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
readingSession?.transition(to: .idle)
|
||||
}
|
||||
|
||||
// 记录翻页到 JumpSession
|
||||
runtime.locationCoordinator.recordPageChangeIfNeeded()
|
||||
}
|
||||
|
||||
/// 屏幕方向即将变化回调,捕获待恢复的展示位置
|
||||
public func readerViewOrientationWillChange(readerView: RDReaderView, isLandscape: Bool) {
|
||||
_ = isLandscape
|
||||
runtime.viewportMonitor.capturePendingPresentationRestoreLocation()
|
||||
}
|
||||
|
||||
/// 检测页面尺寸变化并触发重新分页(避免布局错乱)
|
||||
private func reconcileTextPaginationSizeIfNeeded(for pageNum: Int) {
|
||||
guard textBook != nil || readerContext.bookPageMap != nil,
|
||||
!isRepaginating,
|
||||
@@ -365,12 +362,12 @@ extension RDEPUBReaderController: RDReaderDataSource, RDReaderDelegate {
|
||||
}
|
||||
|
||||
isReconcilingTextPaginationSize = true
|
||||
// 使用防抖延迟,避免连续快速点击字号时重复触发
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||
guard let self else { return }
|
||||
self.isReconcilingTextPaginationSize = false
|
||||
guard (self.textBook != nil || self.readerContext.bookPageMap != nil), !self.isRepaginating else { return }
|
||||
// 再次检查尺寸是否仍然变化,避免过期的重新分页
|
||||
|
||||
let currentSize = self.readerView.resolvedSinglePageSize(pageNum: self.readerView.currentPage)
|
||||
guard currentSize.width > 0, currentSize.height > 0 else { return }
|
||||
let stillChanged = abs(currentSize.width - self.lastTextPaginationPageSize!.width) > 0.5
|
||||
|
||||
Reference in New Issue
Block a user