- 实现EPUB阅读器搜索功能及选中注释功能 - 优化CFI模块,修复代码审查发现的11个问题 - 实现大书远距目录跳转与后台补全优化方案 - 优化设置面板与章节运行时联动 - 重构及大量改进优化
22 KiB
EPUBUI 模块代码级参考文档
最后更新:2026-06-18
1. 模块概述
EPUBUI 是 ReadViewSDK 的用户界面层,位于架构最顶层。它包含阅读器控制器、协调器模式、章节运行时系统、设置面板、文本页面渲染和标注管理等子系统。
文件清单(~60 个 Swift 文件):
| 子系统 | 目录 | 核心文件 | 职责 |
|---|---|---|---|
| 阅读器控制器 | EPUBUI/ |
RDEPUBReaderController*.swift |
主控制器及其扩展 |
| 协调器 | ReaderController/ |
RDEPUBReader*Coordinator.swift |
各功能域协调器 |
| 运行时 | ReaderController/ |
RDEPUBReaderRuntime.swift, RDEPUBReaderContext.swift |
运行时状态与依赖 |
| 章节运行时 | ReaderController/ChapterRuntime/ |
RDEPUBChapter*.swift |
按需加载、缓存、页图 |
| 设置 | Settings/ |
RDEPUBReaderSettings*.swift |
配置、主题、设置面板 |
| 文本页面 | TextPage/ |
RDEPUBTextContentView*.swift |
原生文本渲染页面 |
| UI 组件 | EPUBUI/ |
RDEPUBReader*ToolView.swift |
工具栏、搜索栏、目录 |
| 笔记弹层 | Notes/ |
RDEPUBNotePopup*.swift |
脚注弹层 |
| WebView | EPUBUI/ |
RDEPUBWebContentView.swift |
WebView 内容页面 |
2. 阅读器控制器
2.1 RDEPUBReaderController
文件: RDEPUBReaderController.swift + 扩展文件
主控制器,UIViewController 子类,是整个阅读器的入口。
public final class RDEPUBReaderController: UIViewController {
public weak var delegate: RDEPUBReaderDelegate?
public var configuration: RDEPUBReaderConfiguration // 配置(变化时自动重新分页/刷新)
public var currentLocation: RDEPUBLocation? // 当前阅读位置
public var currentPageNumber: Int? // 当前页码(1-based)
public var currentSelection: RDEPUBSelection? // 当前选中文本
public var highlights: [RDEPUBHighlight] // 高亮列表
public var bookmarks: [RDEPUBBookmark] // 书签列表
public var annotations: [RDEPUBAnnotation] // 标注列表(高亮+书签,按时间排序)
public var tableOfContents: [EPUBTableOfContentsItem] // 目录树
public var flattenedTableOfContents: [RDEPUBReaderTableOfContentsItem] // 扁平化目录
let epubURL: URL // EPUB 文件路径
let persistence: RDEPUBReaderPersistence? // 持久化代理
let dependencies: RDEPUBReaderDependencies // 依赖注入
let readerView = RDReaderView() // 翻页容器
}
扩展文件职责:
| 文件 | 职责 |
|---|---|
RDEPUBReaderController+PublicAPI.swift |
公开 API(跳转、搜索、标注、书签) |
RDEPUBReaderController+DataSource.swift |
RDReaderPageProvider 实现 |
RDEPUBReaderController+ContentDelegates.swift |
WebView/TextContentView 代理 |
RDEPUBReaderController+RenderSupport.swift |
渲染辅助(WebView/TextContent 创建) |
RDEPUBReaderController+RuntimeBridge.swift |
Runtime 桥接 |
RDEPUBReaderController+TableOfContents.swift |
目录处理 |
2.2 RDEPUBReaderDelegate
文件: RDEPUBReaderDelegate.swift
public protocol RDEPUBReaderDelegate: AnyObject {
func epubReader(_ reader: UIViewController, didOpen publication: RDEPUBPublication)
func epubReader(_ reader: UIViewController, didUpdateLocation location: RDEPUBLocation)
func epubReaderDidReachEnd(_ reader: UIViewController)
func epubReader(_ reader: UIViewController, didChangeSelection selection: RDEPUBSelection?)
func epubReader(_ reader: UIViewController, didUpdateHighlights highlights: [RDEPUBHighlight])
func epubReader(_ reader: UIViewController, didUpdateBookmarks bookmarks: [RDEPUBBookmark])
func epubReader(_ reader: UIViewController, didUpdateSearchResult result: RDEPUBSearchResult?)
func epubReader(_ reader: UIViewController, didChangeCurrentSearchMatch match: RDEPUBSearchMatch?)
func epubReader(_ reader: UIViewController, didUpdateCurrentTableOfContentsItem item: RDEPUBReaderTableOfContentsItem?)
func epubReader(_ reader: UIViewController, didActivateExternalLink url: URL)
func epubReader(_ reader: UIViewController, shouldOpenExternalURL url: URL) -> Bool
func epubReader(_ reader: UIViewController, didFailWithError error: Error)
func epubReader(_ reader: UIViewController, configureTopToolView topToolView: RDEPUBReaderTopToolView)
}
所有方法均有默认空实现。
2.3 RDEPUBReaderPersistence
文件: RDEPUBReaderPersistence.swift
持久化协议,由宿主 App 实现。
public protocol RDEPUBReaderPersistence: AnyObject {
func loadLocation(for bookIdentifier: String) -> RDEPUBLocation?
func saveLocation(_ location: RDEPUBLocation, for bookIdentifier: String)
func loadBookmarks(for bookIdentifier: String) -> [RDEPUBBookmark]
func saveBookmarks(_ bookmarks: [RDEPUBBookmark], for bookIdentifier: String)
func loadHighlights(for bookIdentifier: String) -> [RDEPUBHighlight]
func saveHighlights(_ highlights: [RDEPUBHighlight], for bookIdentifier: String)
}
3. 运行时与上下文
3.1 RDEPUBReaderContext
文件: ReaderController/RDEPUBReaderContext.swift
阅读器的共享上下文,持有所有运行时状态。协调器通过 unowned 引用访问。
final class RDEPUBReaderContext {
weak var controller: RDEPUBReaderController?
weak var readerView: RDReaderView?
var dependencies: RDEPUBReaderDependencies
var runtime: RDEPUBReaderRuntime?
// 解析状态
var parser: RDEPUBParser?
var publication: RDEPUBPublication?
var readingSession: RDEPUBReadingSession?
var textBook: RDEPUBTextBook?
var bookPageMap: RDEPUBBookPageMap?
// 标注状态
var activeBookmarks: [RDEPUBBookmark]
var activeHighlights: [RDEPUBHighlight]
var currentBookIdentifier: String?
var selectionState: RDEPUBSelectionState
var currentSelection: RDEPUBSelection?
// 搜索状态
var searchState: RDEPUBSearchState?
}
3.2 RDEPUBReaderDependencies
文件: ReaderController/RDEPUBReaderDependencies.swift
依赖注入容器,支持测试替身。
public struct RDEPUBReaderDependencies {
public var environment: any RDEPUBReaderDisplayEnvironment
public var makeParser: () -> RDEPUBParser
public var makePaginator: () -> RDEPUBPaginator
public var makeTextBookBuilder: (RDEPUBTextRenderer, RDEPUBTextBookCache?, RDEPUBTextLayoutConfig) -> RDEPUBTextBookBuilder
public var makePlainTextBookBuilder: (RDEPUBTextRenderer, RDEPUBTextLayoutConfig) -> RDPlainTextBookBuilder
public var makeTextRenderer: (RDEPUBTextRenderingEngine) -> RDEPUBTextRenderer
public static var live: RDEPUBReaderDependencies // 默认实现
}
3.3 RDEPUBReaderRuntime
文件: ReaderController/RDEPUBReaderRuntime.swift
运行时管理器,持有所有协调器和子系统。
final class RDEPUBReaderRuntime {
lazy var chapterRuntimeStore = RDEPUBChapterRuntimeStore()
lazy var summaryDiskCache: RDEPUBChapterSummaryDiskCache
lazy var chapterLoader: RDEPUBChapterLoader
lazy var pageResolver: RDEPUBPageResolver
lazy var loadCoordinator: RDEPUBReaderLoadCoordinator
lazy var paginationCoordinator: RDEPUBReaderPaginationCoordinator
lazy var locationCoordinator: RDEPUBReaderLocationCoordinator
lazy var searchCoordinator: RDEPUBReaderSearchCoordinator
lazy var chromeCoordinator: RDEPUBReaderChromeCoordinator
lazy var annotationCoordinator: RDEPUBReaderAnnotationCoordinator
lazy var viewportMonitor: RDEPUBReaderViewportMonitor
lazy var jumpSessionManager: RDEPUBJumpSessionManager
lazy var backgroundPriorityManager: RDEPUBBackgroundPriorityManager
lazy var backgroundCoverageStore: RDEPUBBackgroundCoverageStore
lazy var reconciliationCoordinator: RDEPUBPageMapReconciliationCoordinator
var isSettingsPanelOpen: Bool
var needsFullRepaginationAfterSettingsClose: Bool
}
4. 协调器子系统
采用 Coordinator 模式,每个功能域由独立的协调器管理。
4.1 RDEPUBReaderLoadCoordinator
文件: ReaderController/RDEPUBReaderLoadCoordinator.swift
负责初始加载流程:解析 EPUB → 创建 Publication → 恢复阅读位置。
final class RDEPUBReaderLoadCoordinator {
func startInitialLoadIfNeeded() // 开始初始加载
func loadPublication() // 解析 EPUB 文件
func applyParsedPublication(...) // 应用解析结果
}
4.2 RDEPUBReaderPaginationCoordinator
文件: ReaderController/RDEPUBReaderPaginationCoordinator.swift
负责分页调度:WebView 分页 → 文本构建 → 页图生成。
final class RDEPUBReaderPaginationCoordinator {
func startPagination(...) // 开始分页
func applyPageCounts(...) // 应用页数结果
func repaginatePreservingCurrentLocation() // 重新分页(保持位置)
}
4.3 RDEPUBReaderLocationCoordinator
文件: ReaderController/RDEPUBReaderLocationCoordinator.swift
负责位置管理:恢复位置、记录位置变化、目录跳转。
final class RDEPUBReaderLocationCoordinator {
func restoreReadingLocation(_ location: RDEPUBLocation, animated: Bool, ...) -> Bool
func currentVisibleLocation() -> RDEPUBLocation?
func recordPageChangeIfNeeded()
}
4.4 RDEPUBReaderSearchCoordinator
文件: ReaderController/RDEPUBReaderSearchCoordinator.swift
负责搜索管理:执行搜索、导航到匹配项、清除搜索。
final class RDEPUBReaderSearchCoordinator {
func search(keyword: String)
func searchNext() -> Bool
func searchPrevious() -> Bool
func selectSearchMatch(at index: Int) -> Bool
func clearSearch()
}
4.5 RDEPUBReaderChromeCoordinator
文件: ReaderController/RDEPUBReaderChromeCoordinator.swift
负责 UI Chrome(工具栏、搜索栏)的创建和状态更新。
final class RDEPUBReaderChromeCoordinator {
func makeTopToolView() -> RDEPUBReaderTopToolView
func makeBottomToolView() -> RDEPUBReaderBottomToolView
func updateReaderChrome()
func toggleSearchBar()
func presentTableOfContents()
func presentSettings()
}
4.6 RDEPUBReaderAnnotationCoordinator
文件: ReaderController/RDEPUBReaderAnnotationCoordinator.swift
负责标注管理:高亮、书签的增删改查。
final class RDEPUBReaderAnnotationCoordinator {
func addHighlight(from selection: RDEPUBSelection?, color: String, note: String?) -> RDEPUBHighlight?
func removeHighlight(withID id: String)
func addBookmark(for location: RDEPUBLocation, ...) -> RDEPUBBookmark?
func removeBookmark(withID id: String)
func toggleBookmark() -> Bool
func updateCurrentSelection(_ selection: RDEPUBSelection?)
}
5. 章节运行时子系统
目录: ReaderController/ChapterRuntime/
按需加载章节、管理章节缓存、维护全书页图。
5.1 RDEPUBChapterRuntimeStore
文件: ChapterRuntime/RDEPUBChapterRuntimeStore.swift
章节数据的核心存储,管理内存缓存和窗口。
final class RDEPUBChapterRuntimeStore {
let chapterLoadQueue: DispatchQueue // 后台加载队列
private(set) var currentSpineIndex: Int?
private(set) var windowSpineIndices: [Int] // 当前窗口内的 spine 索引
func chapterData(for spineIndex: Int) -> RDEPUBRuntimeChapter?
func insertChapter(_ chapter: RDEPUBRuntimeChapter)
func setCurrentChapter(spineIndex: Int, totalSpineCount: Int, windowRadius: Int)
func evictableSpineIndices() -> [Int]
func evict(spineIndex: Int)
}
5.2 RDEPUBChapterLoader
文件: ChapterRuntime/RDEPUBChapterLoader.swift
章节按需加载器,支持优先级和磁盘摘要缓存。
final class RDEPUBChapterLoader {
enum LoadPriority {
case navigation // 导航(最高优先级)
case preview // 预览
case prefetch // 预取(最低优先级)
}
func loadChapter(
spineIndex: Int,
store: RDEPUBChapterRuntimeStore,
priority: LoadPriority,
completion: @escaping (Result<RDEPUBRuntimeChapter, Error>) -> Void
)
}
加载流程:
- 检查内存缓存(
RDEPUBChapterDataCache) - 检查页数缓存(
RDEPUBPageCountCache) - 检查磁盘摘要缓存(
RDEPUBChapterSummaryDiskCache) - 构建章节(排版 + 分页)
- 写入缓存
5.3 RDEPUBChapterWindowCoordinator
文件: ChapterRuntime/RDEPUBChapterWindowCoordinator.swift
管理章节窗口(当前章 + 前后各 N 章),协调加载和驱逐。
final class RDEPUBChapterWindowCoordinator {
private(set) var currentSnapshot: RDEPUBChapterWindowSnapshot?
var onSnapshotChanged: ((RDEPUBChapterWindowSnapshot) -> Void)?
func openBook(at targetSpineIndex: Int, restoreChapterOffset: Int?)
func navigateToChapter(at spineIndex: Int)
}
5.4 RDEPUBBookPageMap
文件: ChapterRuntime/RDEPUBBookPageMap.swift
全书页图,映射绝对页码 ↔ spine 索引 + 本地页码。
struct RDEPUBBookPageMap {
let entries: [RDEPUBBookPageMapEntry]
let totalPages: Int
func absolutePageIndex(spineIndex: Int, localPageIndex: Int) -> Int?
func spineIndex(forAbsolutePage absolutePage: Int) -> Int?
func localPageIndex(forAbsolutePage absolutePage: Int) -> Int?
func chapterIndex(forSpineIndex spineIndex: Int) -> Int?
}
5.5 RDEPUBPageResolver
文件: ChapterRuntime/RDEPUBPageResolver.swift
将绝对页码解析为具体的页面数据。
final class RDEPUBPageResolver {
func resolvePage(absolutePageIndex: Int) -> RDEPUBResolvedPage?
}
struct RDEPUBResolvedPage {
let page: RDEPUBTextPage
let chapter: RDEPUBRuntimeChapter
let chapterIndex: Int
}
5.6 其他 ChapterRuntime 组件
| 文件 | 职责 |
|---|---|
RDEPUBRuntimeChapter.swift |
运行时章节模型(包含分页后的页面列表) |
RDEPUBRuntimePageCount.swift |
页数缓存模型 |
RDEPUBChapterDataCache.swift |
章节数据内存缓存(NSCache) |
RDEPUBPageCountCache.swift |
页数缓存 |
RDEPUBChapterCacheKey.swift |
缓存键(pageSize + fontSize + lineHeight) |
RDEPUBChapterSummaryDiskCache.swift |
章节摘要磁盘缓存 |
RDEPUBChapterWindowSnapshot.swift |
窗口快照(当前可见章节集合) |
RDEPUBChapterLocation.swift |
章节位置模型 |
RDEPUBChapterOffsetMap.swift |
章节偏移映射 |
RDEPUBBackgroundTrace.swift |
后台任务追踪日志 |
String+SHA256.swift |
SHA256 哈希扩展 |
6. 设置子系统
目录: Settings/
6.1 RDEPUBReaderConfiguration
文件: Settings/RDEPUBReaderConfiguration.swift
阅读器配置模型。
public struct RDEPUBReaderConfiguration: Equatable {
public var fontSize: CGFloat
public var lineHeightMultiple: CGFloat
public var fontChoice: RDEPUBReaderFontChoice
public var numberOfColumns: Int
public var columnGap: CGFloat
// ... 更多配置项
}
public enum RDEPUBReaderFontChoice: String, Codable, CaseIterable {
case system // 系统字体
case serif // 宋体
case rounded // 圆体
case monospaced // 等宽
}
public enum RDEPUBTextRenderingEngine: Equatable {
case dtCoreText
}
6.2 RDEPUBReaderSettings
文件: Settings/RDEPUBReaderSettings.swift
设置状态管理。
6.3 RDEPUBReaderSettingsViewController
文件: Settings/RDEPUBReaderSettingsViewController.swift
设置面板 VC(字体大小、行高、字体选择、主题等)。
6.4 RDEPUBReaderTheme
文件: Settings/RDEPUBReaderTheme.swift
主题定义(日间/夜间/护眼等)。
7. 文本页面子系统
目录: TextPage/
原生文本渲染模式(textReflowable)的页面视图。
7.1 RDEPUBTextContentView
文件: TextPage/RDEPUBTextContentView.swift
文本内容视图,使用 CoreText 渲染 NSAttributedString。
final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate {
weak var delegate: RDEPUBTextContentViewDelegate?
func configure(page: RDEPUBTextPage, ...)
func applyHighlights(_ highlights: [RDEPUBHighlight])
func applySearchState(_ state: RDEPUBSearchState?)
}
7.2 RDEPUBTextPageRenderView
文件: TextPage/RDEPUBTextPageRenderView.swift
CoreText 渲染视图,绘制 NSAttributedString 到屏幕上。
7.3 RDEPUBTextAnnotationOverlay
文件: TextPage/RDEPUBTextAnnotationOverlay.swift
标注叠加层,绘制高亮和下划线。
7.4 RDEPUBSelectionOverlayView
文件: TextPage/RDEPUBSelectionOverlayView.swift
文本选择叠加层(选择手柄)。
7.5 RDEPUBTextSelectionController
文件: TextPage/RDEPUBTextSelectionController.swift
文本选择手势控制器。
7.6 RDEPUBPageInteractionController
文件: TextPage/RDEPUBPageInteractionController.swift
页面交互控制器(长按选择、点击标注等)。
7.7 RDEPUBPageLayoutSnapshot
文件: TextPage/RDEPUBPageLayoutSnapshot.swift
页面布局快照(用于选择定位)。
7.8 RDEPUBTextPageDecorationView
文件: TextPage/RDEPUBTextPageDecorationView.swift
页面装饰视图(页码、页眉等)。
8. UI 组件
8.1 工具栏
| 文件 | 职责 |
|---|---|
RDEPUBReaderTopToolView.swift |
顶部工具栏(返回、搜索、书签) |
RDEPUBReaderBottomToolView.swift |
底部工具栏(目录、书签、高亮、设置) |
RDEPUBReaderToolView.swift |
工具栏基类 |
8.2 搜索栏
文件: RDEPUBReaderSearchBarView.swift
搜索输入栏(输入框 + 上一个/下一个 + 关闭)。
8.3 目录
文件: RDEPUBReaderChapterListController.swift
目录列表 VC,支持多级目录展开。
文件: RDEPUBReaderTableOfContentsItem.swift
目录项模型。
8.4 高亮管理
文件: RDEPUBReaderHighlightsViewController.swift
高亮列表 VC。
8.5 笔记弹层
目录: Notes/
| 文件 | 职责 |
|---|---|
RDEPUBNotePopupCoordinator.swift |
脚注弹层协调器 |
RDEPUBNotePopupViewController.swift |
脚注弹层 VC |
8.6 WebView 内容视图
文件: RDEPUBWebContentView.swift
WebView 模式的内容页面视图。
8.7 装饰叠加层
文件: RDEPUBWebDecorationOverlayView.swift
WebView 模式的装饰叠加层(高亮、搜索高亮)。
9. 状态模型
9.1 RDEPUBSelectionState
enum RDEPUBSelectionState: Equatable {
case idle // 无选择
case selecting(anchor: Int) // 选择中
case selected(RDEPUBSelection) // 已选择
case committingAction(RDEPUBSelection, action: RDEPUBAnnotationMenuAction) // 执行操作中
}
9.2 RDEPUBReaderUIState
struct RDEPUBReaderUIState {
let canToggleBookmark: Bool
let hasBookmarkAtCurrentLocation: Bool
let canShowBookmarks: Bool
let canAddHighlight: Bool
let canShowHighlights: Bool
let showsTableOfContents: Bool
let allowsHighlights: Bool
let showsSettingsPanel: Bool
}
9.3 RDEPUBViewportTypes
文件: RDEPUBViewportTypes.swift
视口相关模型。
10. 其他组件
10.1 RDEPUBReaderViewportMonitor
文件: ReaderController/RDEPUBReaderViewportMonitor.swift
视口变化监听器(旋转、尺寸变化)。
10.2 RDEPUBJumpSession
文件: ReaderController/RDEPUBJumpSession.swift
跳转会话管理(远距跳转时的加载状态)。
10.3 RDEPUBBackgroundPriorityPolicy
文件: ReaderController/RDEPUBBackgroundPriorityPolicy.swift
后台加载优先级策略。
10.4 RDEPUBBackgroundCoverageStore
文件: ReaderController/RDEPUBBackgroundCoverageStore.swift
后台加载覆盖范围追踪。
10.5 RDEPUBPageMapReconciliationCoordinator
文件: ReaderController/RDEPUBPageMapReconciliationCoordinator.swift
页图协调器(按需分页与全量分页的结果合并)。
10.6 RDURLReaderController
文件: RDURLReaderController.swift
URL 阅读器控制器(用于打开单个 URL)。
10.7 UIColor+RDEPUBHex
文件: UIColor+RDEPUBHex.swift
UIColor 十六进制扩展。
11. 设计模式总结
| 模式 | 应用 |
|---|---|
| Coordinator 模式 | 7 个独立协调器分管各功能域 |
| Context 模式 | RDEPUBReaderContext 共享上下文,避免循环依赖 |
| 依赖注入 | RDEPUBReaderDependencies 工厂方法注入 |
| 按需加载 | RDEPUBChapterLoader 章节级按需加载 |
| 窗口管理 | RDEPUBChapterWindowCoordinator 滑动窗口驱逐 |
| 快照管理 | RDEPUBBookPageMap 全书页图快照 |
| 状态机 | RDEPUBSelectionState 选择状态管理 |
| 磁盘缓存 | RDEPUBChapterSummaryDiskCache 章节摘要持久化 |
12. 数据流图
用户打开 EPUB
│
▼
RDEPUBReaderController.init(epubURL:)
│
▼ viewDidLoad()
RDEPUBReaderLoadCoordinator.startInitialLoadIfNeeded()
│
├── RDEPUBParser.parse(epubURL:) → 解析 EPUB
├── RDEPUBPublication 创建
├── 恢复阅读位置(persistence.loadLocation)
│
▼
RDEPUBReaderRuntime.applyParsedPublication()
│
├── 根据 readingProfile 选择渲染模式
│ ├── webInteractive → WebView 分页 → RDEPUBPaginator
│ └── textReflowable → 文本构建 → RDEPUBTextBookBuilder
│
├── RDEPUBChapterWindowCoordinator.openBook()
│ └── RDEPUBChapterLoader.loadChapter() → 按需加载
│
├── RDEPUBBookPageMap 生成
│
└── RDReaderView.transitionToPage() → 显示页面
用户翻页
│
▼
RDReaderView.currentPage 变化
│
├── RDEPUBReaderLocationCoordinator.recordPageChangeIfNeeded()
│ └── persistence.saveLocation()
│
├── RDEPUBChapterWindowCoordinator 检查是否需要加载新章节
│
└── RDEPUBReaderChromeCoordinator.updateReaderChrome()