feat(reader): 实现大书快速进入与增量分页路径
- PaginationCoordinator: 新增 textReflowable 快速进入路径,支持增量章节构建 与 staged 合并策略,避免整书一次性构建的主线程阻塞 - TextBookBuilder: 重构为支持单章构建与增量合并模式 - ChapterPageCounter: 优化分页计数逻辑,支持章节级分页缓存 - PageFrameFactory: 新增 CoreText 页面帧工厂,提升排版性能 - ReaderContext/ReaderRuntime: 适配增量分页状态管理 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
488350d956
commit
c76beed03c
@@ -25,6 +25,12 @@ import UIKit
|
||||
/// navigationController?.pushViewController(controller, animated: true)
|
||||
/// ```
|
||||
public final class RDURLReaderController: UIViewController {
|
||||
private struct PendingDemoPageRequest {
|
||||
let pageNumber: Int
|
||||
let animated: Bool
|
||||
var attemptCount: Int
|
||||
}
|
||||
|
||||
/// 书籍文件 URL
|
||||
private let bookURL: URL
|
||||
/// EPUB 阅读器配置(字体、行距、主题等)
|
||||
@@ -41,6 +47,10 @@ public final class RDURLReaderController: UIViewController {
|
||||
label.text = "reader=opening"
|
||||
return label
|
||||
}()
|
||||
private var pendingDemoPageRequest: PendingDemoPageRequest?
|
||||
private var isRetryingPendingDemoPage = false
|
||||
private let maxPendingDemoPageAttempts = 24
|
||||
private let pendingDemoPageRetryDelay: TimeInterval = 0.25
|
||||
|
||||
/// 初始化方法
|
||||
/// - Parameters:
|
||||
@@ -88,9 +98,12 @@ public final class RDURLReaderController: UIViewController {
|
||||
/// - Returns: 是否成功跳转
|
||||
@discardableResult
|
||||
public func goToDemoPage(_ pageNumber: Int, animated: Bool = false) -> Bool {
|
||||
let moved = readerController?.go(toPageNumber: pageNumber, animated: animated) ?? false
|
||||
let moved = performDemoPageNavigation(pageNumber, animated: animated)
|
||||
if moved {
|
||||
emitDemoState(prefix: "page=\(pageNumber)")
|
||||
pendingDemoPageRequest = nil
|
||||
isRetryingPendingDemoPage = false
|
||||
} else {
|
||||
queuePendingDemoPageNavigation(pageNumber, animated: animated)
|
||||
}
|
||||
return moved
|
||||
}
|
||||
@@ -187,6 +200,73 @@ public final class RDURLReaderController: UIViewController {
|
||||
embeddedController as? RDEPUBReaderController
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func performDemoPageNavigation(_ pageNumber: Int, animated: Bool) -> Bool {
|
||||
guard let readerController else { return false }
|
||||
guard pageNumber > 0 else { return false }
|
||||
|
||||
let totalPages = readerController.textBook?.pages.count ?? readerController.activePages.count
|
||||
let numPages = readerController.readerView.numberOfPages()
|
||||
print("[ReadViewDemo] performDemoPageNavigation(\(pageNumber)): textBook.pages=\(totalPages), readerView.numberOfPages=\(numPages), currentPage=\(readerController.readerView.currentPage)")
|
||||
guard pageNumber <= totalPages else { return false }
|
||||
|
||||
readerController.readerView.transitionToPage(pageNum: pageNumber - 1, animated: animated)
|
||||
print("[ReadViewDemo] performDemoPageNavigation(\(pageNumber)): after transition, currentPage=\(readerController.readerView.currentPage)")
|
||||
emitDemoState(prefix: "page=\(pageNumber)")
|
||||
return true
|
||||
}
|
||||
|
||||
private func queuePendingDemoPageNavigation(_ pageNumber: Int, animated: Bool) {
|
||||
if let pendingDemoPageRequest,
|
||||
pendingDemoPageRequest.pageNumber == pageNumber,
|
||||
pendingDemoPageRequest.animated == animated {
|
||||
return
|
||||
}
|
||||
|
||||
pendingDemoPageRequest = PendingDemoPageRequest(
|
||||
pageNumber: pageNumber,
|
||||
animated: animated,
|
||||
attemptCount: 0
|
||||
)
|
||||
retryPendingDemoPageIfNeeded()
|
||||
}
|
||||
|
||||
private func retryPendingDemoPageIfNeeded() {
|
||||
guard !isRetryingPendingDemoPage else { return }
|
||||
guard pendingDemoPageRequest != nil else { return }
|
||||
|
||||
isRetryingPendingDemoPage = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + pendingDemoPageRetryDelay) { [weak self] in
|
||||
self?.attemptPendingDemoPageNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
private func attemptPendingDemoPageNavigation() {
|
||||
guard var pendingDemoPageRequest else {
|
||||
isRetryingPendingDemoPage = false
|
||||
return
|
||||
}
|
||||
|
||||
if performDemoPageNavigation(pendingDemoPageRequest.pageNumber, animated: pendingDemoPageRequest.animated) {
|
||||
self.pendingDemoPageRequest = nil
|
||||
isRetryingPendingDemoPage = false
|
||||
return
|
||||
}
|
||||
|
||||
pendingDemoPageRequest.attemptCount += 1
|
||||
if pendingDemoPageRequest.attemptCount >= maxPendingDemoPageAttempts {
|
||||
self.pendingDemoPageRequest = nil
|
||||
isRetryingPendingDemoPage = false
|
||||
print("[ReadViewDemo] automation page=\(pendingDemoPageRequest.pageNumber) failed after \(pendingDemoPageRequest.attemptCount) attempts")
|
||||
return
|
||||
}
|
||||
|
||||
self.pendingDemoPageRequest = pendingDemoPageRequest
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + pendingDemoPageRetryDelay) { [weak self] in
|
||||
self?.attemptPendingDemoPageNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
/// 输出当前阅读器状态日志(Demo 调试用)
|
||||
private func logDemoState(prefix: String) {
|
||||
guard let readerController else { return }
|
||||
@@ -259,7 +339,13 @@ public final class RDURLReaderController: UIViewController {
|
||||
}
|
||||
|
||||
extension RDURLReaderController: RDEPUBReaderDelegate {
|
||||
public func epubReader(_ reader: UIViewController, didOpen publication: RDEPUBPublication) {
|
||||
retryPendingDemoPageIfNeeded()
|
||||
emitDemoState()
|
||||
}
|
||||
|
||||
public func epubReader(_ reader: UIViewController, didUpdateLocation location: RDEPUBLocation) {
|
||||
retryPendingDemoPageIfNeeded()
|
||||
emitDemoState()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user