ReadViewSDK/Sources/RDReaderView/EPUBUI/RDEPUBReaderController+DataSource.swift
shenlei 0e7c0577e3 feat: add in-reader search and restructure documentation
- Add RDEPUBReaderSearchBarView with animated show/hide, keyword
  navigation, and match counting integrated into the reader controller
- Restructure docs: replace scattered design docs with consolidated
  BUSINESS_LOGIC.md and UML_CLASS_DIAGRAMS.md; update ARCHITECTURE.md
- Add SearchTests and FanrenParseTimeTest; enhance LargeBookOnDemandTests
- Add scripts/run_ui_regression.sh and summarize_ui_results.py for
  automated UI test execution and reporting

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-05 17:34:50 +08:00

180 lines
7.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
contentView.configure(
page: resolvedPage.page,
pageNumber: pageNum + 1,
totalPages: pageCountOfReaderView(readerView: readerView),
configuration: configuration,
highlights: textHighlights(for: resolvedPage.page),
searchState: searchState
)
return contentView
}
}
if let textBook, let page = textBook.page(at: pageNum + 1) {
let contentView = (containerView as? RDEPUBTextContentView) ?? RDEPUBTextContentView()
contentView.delegate = self
contentView.configure(
page: page,
pageNumber: pageNum + 1,
totalPages: textBook.pages.count,
configuration: configuration,
highlights: textHighlights(for: page),
searchState: searchState
)
return contentView
}
guard let publication,
let request = request(for: pageNum) else {
return containerView ?? UIView()
}
let contentView = (containerView as? RDEPUBWebContentView) ?? RDEPUBWebContentView()
contentView.delegate = self
contentView.configure(
publication: publication,
request: request,
pageNumber: pageNum + 1,
totalPages: activePages.count,
theme: configuration.theme
)
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) {
return chapterData.highlights(on: page, from: activeHighlights)
}
guard let publication else {
return activeHighlights.filter { $0.location.href == page.href }
}
let pageHref = publication.resourceResolver.normalizedHref(page.href) ?? page.href
return activeHighlights.filter {
(publication.resourceResolver.normalizedHref($0.location.href) ?? $0.location.href) == pageHref
}
}
/// 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)
// map
runtime.applyPendingFullPageMapIfNeeded()
if readerContext.bookPageMap != nil {
_ = runtime.prepareOnDemandChapter(forAbsolutePageNumber: pageNum + 1)
runtime.extendPartialBookPageMapIfNeeded(currentPageNumber: pageNum + 1)
}
let totalPages = pageCountOfReaderView(readerView: readerView)
if totalPages > 0, pageNum == totalPages - 1 {
delegate?.epubReaderDidReachEnd(self)
}
if (textBook != nil || readerContext.bookPageMap != nil),
let location = resolvedTextLocation(forPageNumber: pageNum + 1) {
persist(location: location)
synchronizeTextReadingState(pageNumber: pageNum + 1, location: location)
return
}
if let location = fallbackLocation(for: pageNum) {
persist(location: location)
}
if readingSession?.navigatorState == .jumping || readingSession?.navigatorState == .moving {
readingSession?.transition(to: .idle)
}
}
///
public func readerViewOrientationWillChange(readerView: RDReaderView, isLandscape: Bool) {
_ = isLandscape
runtime.viewportMonitor.capturePendingPresentationRestoreLocation()
}
///
private func reconcileTextPaginationSizeIfNeeded(for pageNum: Int) {
guard textBook != nil || readerContext.bookPageMap != nil,
!isRepaginating,
!isReconcilingTextPaginationSize,
pageNum >= 0,
let lastTextPaginationPageSize else {
return
}
let resolvedSize = readerView.resolvedSinglePageSize(pageNum: pageNum)
guard resolvedSize.width > 0,
resolvedSize.height > 0 else {
return
}
let sizeChanged = abs(resolvedSize.width - lastTextPaginationPageSize.width) > 0.5
|| abs(resolvedSize.height - lastTextPaginationPageSize.height) > 0.5
guard sizeChanged else {
return
}
isReconcilingTextPaginationSize = true
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.isReconcilingTextPaginationSize = false
guard (self.textBook != nil || self.readerContext.bookPageMap != nil), !self.isRepaginating else { return }
self.repaginatePreservingCurrentLocation()
}
}
}