Compare commits
6
Commits
47fe2dc450
..
0.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0efe3f2cc | ||
|
|
ca408c20ab | ||
|
|
5ae7823ef8 | ||
|
|
bd6e915fbd | ||
|
|
7132e4952b | ||
|
|
f796823db8 |
+1505
-1501
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ public struct RDEPUBNavigatorLayoutContext: Equatable {
|
||||
pagesPerScreen: Int = 1,
|
||||
safeAreaInsets: UIEdgeInsets = .zero,
|
||||
userInterfaceIdiom: UIUserInterfaceIdiom = .phone,
|
||||
reflowableContentInsets: UIEdgeInsets = UIEdgeInsets(top: 40, left: 16, bottom: 40, right: 16)
|
||||
reflowableContentInsets: UIEdgeInsets = RDEPUBSafeArea.defaultReflowableContentInsets()
|
||||
) {
|
||||
self.containerSize = containerSize
|
||||
self.pagesPerScreen = max(1, pagesPerScreen)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import UIKit
|
||||
|
||||
/// Device-level safe area lookup modeled after GKNavigationBarSwift.
|
||||
/// Reads the real safe area from the key window so correct values are
|
||||
/// available even before a view has been laid out in the hierarchy.
|
||||
/// Must be called on the main thread.
|
||||
public enum RDEPUBSafeArea {
|
||||
|
||||
/// Minimum text margins applied when the device safe area on an edge is
|
||||
/// smaller (e.g. no notch / no home indicator). These are aesthetic
|
||||
/// paddings, not approximations of the safe area itself.
|
||||
public static let minimumVerticalTextMargin: CGFloat = 20
|
||||
|
||||
public static let minimumHorizontalTextMargin: CGFloat = 16
|
||||
|
||||
public static func keyWindow() -> UIWindow? {
|
||||
let scenes = UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
if let window = scenes
|
||||
.filter({ $0.activationState == .foregroundActive })
|
||||
.flatMap({ $0.windows })
|
||||
.first(where: { $0.isKeyWindow }) {
|
||||
return window
|
||||
}
|
||||
if let window = scenes
|
||||
.flatMap({ $0.windows })
|
||||
.first(where: { $0.isKeyWindow }) {
|
||||
return window
|
||||
}
|
||||
return UIApplication.shared.delegate?.window ?? nil
|
||||
}
|
||||
|
||||
public static func insets() -> UIEdgeInsets {
|
||||
if let window = keyWindow() {
|
||||
return window.safeAreaInsets
|
||||
}
|
||||
// No key window yet (early launch): create a detached window to read
|
||||
// the device safe area, same fallback as GKNavigationBarSwift.
|
||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||
if window.safeAreaInsets.bottom <= 0 {
|
||||
window.rootViewController = UIViewController()
|
||||
}
|
||||
return window.safeAreaInsets
|
||||
}
|
||||
|
||||
/// Prefers insets measured from a view already installed in the hierarchy;
|
||||
/// falls back to the key-window insets when the view is not laid out yet
|
||||
/// and reports .zero.
|
||||
public static func resolve(_ measuredInsets: UIEdgeInsets?) -> UIEdgeInsets {
|
||||
if let measuredInsets, measuredInsets != .zero {
|
||||
return measuredInsets
|
||||
}
|
||||
return insets()
|
||||
}
|
||||
|
||||
/// Default reflowable content insets derived from the live device safe
|
||||
/// area instead of hard-coded heights.
|
||||
public static func defaultReflowableContentInsets() -> UIEdgeInsets {
|
||||
let safe = insets()
|
||||
return UIEdgeInsets(
|
||||
top: max(safe.top, minimumVerticalTextMargin),
|
||||
left: max(safe.left, minimumHorizontalTextMargin),
|
||||
bottom: max(safe.bottom, minimumVerticalTextMargin),
|
||||
right: max(safe.right, minimumHorizontalTextMargin)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -136,8 +136,11 @@ struct RDEPUBAttachmentNormalizer {
|
||||
private static func isFootnoteAttachment(_ attachment: DTTextAttachment) -> Bool {
|
||||
|
||||
let lowercasedClasses = ((attachment.attributes["class"] as? String) ?? "").lowercased()
|
||||
if lowercasedClasses.contains("qqreader-footnote") {
|
||||
return true
|
||||
}
|
||||
let altText = attachment.attributes["alt"] as? String
|
||||
return lowercasedClasses.contains("qqreader-footnote") || hasFootnoteAltText(altText)
|
||||
return hasFootnoteAltText(altText) && isFootnoteSizedImage(attachment.originalSize)
|
||||
}
|
||||
|
||||
private static func isCoverAttachment(_ attachment: DTTextAttachment) -> Bool {
|
||||
@@ -224,10 +227,31 @@ struct RDEPUBAttachmentNormalizer {
|
||||
}
|
||||
let label = fileAttachment.accessibilityLabel
|
||||
let lowercasedLabel = (label ?? "").lowercased()
|
||||
return lowercasedLabel.contains("qqreader-footnote") || hasFootnoteAltText(label)
|
||||
if lowercasedLabel.contains("qqreader-footnote") {
|
||||
return true
|
||||
}
|
||||
let imageSize = fileAttachment.image?.size ?? fileAttachment.bounds.size
|
||||
return hasFootnoteAltText(label) && isFootnoteSizedImage(imageSize)
|
||||
}
|
||||
|
||||
// Footnote images without the qqreader-footnote class are recognized by their
|
||||
// alt text carrying the note body. Short alts ("logo", "图1") are ordinary
|
||||
// accessibility descriptions, and note markers are small inline icons, so both
|
||||
// conditions must hold before an image is shrunk to footnote size.
|
||||
private static let minimumFootnoteAltTextLength = 8
|
||||
|
||||
private static let maximumFootnoteImageDimension: CGFloat = 50
|
||||
|
||||
private static func hasFootnoteAltText(_ text: String?) -> Bool {
|
||||
text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
|
||||
guard let trimmed = text?.trimmingCharacters(in: .whitespacesAndNewlines) else {
|
||||
return false
|
||||
}
|
||||
return trimmed.count >= minimumFootnoteAltTextLength
|
||||
}
|
||||
|
||||
private static func isFootnoteSizedImage(_ size: CGSize) -> Bool {
|
||||
guard size.width > 0, size.height > 0 else { return false }
|
||||
return size.width <= maximumFootnoteImageDimension
|
||||
&& size.height <= maximumFootnoteImageDimension
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ public final class RDURLReaderController: UIViewController {
|
||||
let bookTitle = bookURL.deletingPathExtension().lastPathComponent
|
||||
let pageSize = currentTextPageSize()
|
||||
let renderStyle = currentTextRenderStyle()
|
||||
let safeInsets = view.safeAreaInsets
|
||||
let safeInsets = RDEPUBSafeArea.resolve(view.safeAreaInsets)
|
||||
let edgeInsets = UIEdgeInsets(
|
||||
top: max(epubConfiguration.reflowableContentInsets.top, safeInsets.top),
|
||||
left: max(epubConfiguration.reflowableContentInsets.left, safeInsets.left),
|
||||
|
||||
@@ -65,6 +65,10 @@ final class RDEPUBChapterLoader {
|
||||
completion: @escaping (Result<RDEPUBRuntimeChapter, Error>) -> Void
|
||||
) {
|
||||
if let cached = store.chapterData(for: spineIndex) {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"ChapterLoad",
|
||||
"memory HIT spine=\(spineIndex) pages=\(cached.pages.count) priority=\(priority)"
|
||||
)
|
||||
if let context {
|
||||
scheduleDeferredCFIMapBuildIfNeeded(
|
||||
for: cached,
|
||||
@@ -92,6 +96,10 @@ final class RDEPUBChapterLoader {
|
||||
|
||||
switch registration {
|
||||
case .joined(let existingPriority, let effectivePriority):
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"ChapterLoad",
|
||||
"join pendingLoad spine=\(spineIndex) existing=\(existingPriority) effective=\(effectivePriority)"
|
||||
)
|
||||
return
|
||||
case .created:
|
||||
break
|
||||
@@ -120,6 +128,20 @@ final class RDEPUBChapterLoader {
|
||||
let diskPageRanges = diskSummary?.pageRanges.map { $0.nsRange }
|
||||
let availablePageRanges = precomputedPageRanges ?? diskPageRanges
|
||||
|
||||
let pageRangeSource: String
|
||||
if precomputedPageRanges != nil {
|
||||
pageRangeSource = "HIT(memoryPageCount)"
|
||||
} else if diskPageRanges != nil {
|
||||
pageRangeSource = "HIT(diskSummary)"
|
||||
} else {
|
||||
pageRangeSource = "MISS(fullRender)"
|
||||
}
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"ChapterLoad",
|
||||
"build start spine=\(spineIndex) priority=\(queuePriority) pageRanges=\(pageRangeSource)"
|
||||
)
|
||||
let buildStart = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
do {
|
||||
|
||||
let chapter = try self.buildChapter(
|
||||
@@ -130,6 +152,10 @@ final class RDEPUBChapterLoader {
|
||||
layoutSnapshot: layoutSnapshot
|
||||
)
|
||||
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"ChapterLoad",
|
||||
"build done spine=\(spineIndex) pages=\(chapter.pages.count) elapsedMs=\(Int((CFAbsoluteTimeGetCurrent() - buildStart) * 1000)) pageRanges=\(pageRangeSource)"
|
||||
)
|
||||
store.insertChapter(chapter)
|
||||
let pc = RDEPUBRuntimePageCount(
|
||||
cacheKey: cacheKey,
|
||||
@@ -172,6 +198,10 @@ final class RDEPUBChapterLoader {
|
||||
self.resolvePendingLoad(spineIndex: spineIndex, result: .success(chapter))
|
||||
}
|
||||
} catch {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"ChapterLoad",
|
||||
"build failed spine=\(spineIndex) pageRanges=\(pageRangeSource) error=\(String(describing: error))"
|
||||
)
|
||||
store.endPendingChapterLoad(for: spineIndex)
|
||||
store.markBuilding(false)
|
||||
self.resolvePendingLoad(spineIndex: spineIndex, result: .failure(error))
|
||||
|
||||
+14
-2
@@ -32,6 +32,8 @@ final class RDEPUBChapterWarmupOrchestrator {
|
||||
|
||||
private let prepareRequestDebounceInterval: CFTimeInterval = 0.15
|
||||
|
||||
private static let upcomingChapterLookaheadCount = 2
|
||||
|
||||
init(
|
||||
context: RDEPUBReaderContext,
|
||||
store: RDEPUBChapterRuntimeStore,
|
||||
@@ -392,7 +394,10 @@ final class RDEPUBChapterWarmupOrchestrator {
|
||||
windowRadius: context.configuration.chapterWindowRadius
|
||||
)
|
||||
|
||||
for evictable in store.evictableSpineIndices() {
|
||||
// Keep chapters that maybePrefetchUpcomingChapters is responsible for,
|
||||
// otherwise the two policies evict/rebuild the same chapter in a loop.
|
||||
let retainedLookaheadIndices = upcomingLookaheadSpineIndices(after: spineIndex)
|
||||
for evictable in store.evictableSpineIndices() where !retainedLookaheadIndices.contains(evictable) {
|
||||
store.evict(spineIndex: evictable)
|
||||
}
|
||||
|
||||
@@ -410,11 +415,18 @@ final class RDEPUBChapterWarmupOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
private func upcomingLookaheadSpineIndices(after spineIndex: Int) -> Set<Int> {
|
||||
guard let publication = context.publication else { return [] }
|
||||
let buildableIndices = buildableSpineIndices(in: publication)
|
||||
guard let currentPosition = buildableIndices.firstIndex(of: spineIndex) else { return [] }
|
||||
return Set(buildableIndices.dropFirst(currentPosition + 1).prefix(Self.upcomingChapterLookaheadCount))
|
||||
}
|
||||
|
||||
private func maybePrefetchUpcomingChapters(
|
||||
aroundAbsolutePageNumber pageNumber: Int,
|
||||
in bookPageMap: RDEPUBBookPageMap,
|
||||
threshold: Int = 3,
|
||||
lookaheadChapterCount: Int = 2
|
||||
lookaheadChapterCount: Int = RDEPUBChapterWarmupOrchestrator.upcomingChapterLookaheadCount
|
||||
) {
|
||||
guard let publication = context.publication else { return }
|
||||
let absolutePageIndex = pageNumber - 1
|
||||
|
||||
@@ -160,6 +160,11 @@ final class RDEPUBPresentationRuntime {
|
||||
) {
|
||||
let currentLocation = locationCoordinator.currentVisibleLocation()
|
||||
let livePageBeforeApply = readerView.currentPage + 1
|
||||
// Resolved against the outgoing page map. When it round-trips to the live
|
||||
// page, the location faithfully describes what is on screen, so whatever
|
||||
// page it resolves to in the new map is authoritative even if the two maps
|
||||
// number pages differently (partial-window -> full-book takeover).
|
||||
let oldResolvedPage = currentLocation.flatMap { controller.pageNumber(for: $0) }
|
||||
|
||||
context.textBook = nil
|
||||
applyPageMapToLiveModel(newPageMap)
|
||||
@@ -172,11 +177,12 @@ final class RDEPUBPresentationRuntime {
|
||||
let resolvedTargetPage = controller.pageNumber(for: currentLocation)
|
||||
let shouldTrustResolvedLocation = shouldTrustFullReplaceResolvedPage(
|
||||
resolvedTargetPage,
|
||||
livePageBeforeApply: livePageBeforeApply
|
||||
livePageBeforeApply: livePageBeforeApply,
|
||||
locationMatchesLivePage: oldResolvedPage == livePageBeforeApply
|
||||
)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"applyFullPageMapReplacement decision livePage=\(livePageBeforeApply) resolvedTargetPage=\(resolvedTargetPage ?? -1) trustResolved=\(shouldTrustResolvedLocation) href=\(currentLocation.href)"
|
||||
"applyFullPageMapReplacement decision livePage=\(livePageBeforeApply) oldResolvedPage=\(oldResolvedPage ?? -1) resolvedTargetPage=\(resolvedTargetPage ?? -1) trustResolved=\(shouldTrustResolvedLocation) href=\(currentLocation.href)"
|
||||
)
|
||||
|
||||
if shouldTrustResolvedLocation,
|
||||
@@ -433,9 +439,16 @@ final class RDEPUBPresentationRuntime {
|
||||
|
||||
private func shouldTrustFullReplaceResolvedPage(
|
||||
_ resolvedTargetPage: Int?,
|
||||
livePageBeforeApply: Int
|
||||
livePageBeforeApply: Int,
|
||||
locationMatchesLivePage: Bool
|
||||
) -> Bool {
|
||||
guard let resolvedTargetPage else { return false }
|
||||
if locationMatchesLivePage {
|
||||
return true
|
||||
}
|
||||
// The location did not round-trip to the live page in the outgoing map
|
||||
// (stale persisted location or mid-transition), so only follow it when it
|
||||
// stays next to the page the user is actually looking at.
|
||||
return abs(resolvedTargetPage - livePageBeforeApply) <= 1
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ final class RDEPUBReaderContext {
|
||||
}
|
||||
|
||||
func currentPreferences() -> RDEPUBPreferences {
|
||||
let safeInsets = controller?.view.safeAreaInsets ?? .zero
|
||||
let safeInsets = RDEPUBSafeArea.resolve(controller?.view.safeAreaInsets)
|
||||
return configuration.makePreferences(safeAreaInsets: safeInsets)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ final class RDEPUBReaderEnvironment {
|
||||
return RDEPUBNavigatorLayoutContext(
|
||||
containerSize: resolvedSize,
|
||||
pagesPerScreen: readerView?.pagesPerScreen ?? 1,
|
||||
safeAreaInsets: controller?.view.safeAreaInsets ?? .zero,
|
||||
safeAreaInsets: RDEPUBSafeArea.resolve(controller?.view.safeAreaInsets),
|
||||
userInterfaceIdiom: controller?.traitCollection.userInterfaceIdiom ?? .phone,
|
||||
reflowableContentInsets: configuration.reflowableContentInsets
|
||||
)
|
||||
@@ -68,7 +68,7 @@ final class RDEPUBReaderEnvironment {
|
||||
configuration: RDEPUBReaderConfiguration,
|
||||
pageSize: CGSize
|
||||
) -> RDEPUBTextLayoutConfig {
|
||||
let safeAreaInsets = controller?.view.safeAreaInsets ?? .zero
|
||||
let safeAreaInsets = RDEPUBSafeArea.resolve(controller?.view.safeAreaInsets)
|
||||
return RDEPUBTextLayoutConfig(
|
||||
frameWidth: max(pageSize.width, 1),
|
||||
frameHeight: max(pageSize.height, 1),
|
||||
|
||||
@@ -109,7 +109,7 @@ public struct RDEPUBReaderConfiguration: Equatable {
|
||||
showsTableOfContents: Bool = true,
|
||||
allowsHighlights: Bool = true,
|
||||
showsSettingsPanel: Bool = true,
|
||||
reflowableContentInsets: UIEdgeInsets = UIEdgeInsets(top: 40, left: 16, bottom: 40, right: 16),
|
||||
reflowableContentInsets: UIEdgeInsets = RDEPUBSafeArea.defaultReflowableContentInsets(),
|
||||
fixedContentInset: UIEdgeInsets = .zero,
|
||||
theme: RDEPUBReaderTheme = .light,
|
||||
darkImageAdjustmentEnabled: Bool = true,
|
||||
@@ -172,11 +172,13 @@ extension RDEPUBReaderConfiguration {
|
||||
func makePreferences(safeAreaInsets: UIEdgeInsets = .zero) -> RDEPUBPreferences {
|
||||
// Use the larger of reflowableContentInsets and safeAreaInsets for each edge
|
||||
// to prevent content from being hidden under Dynamic Island / home indicator.
|
||||
// Falls back to the key-window safe area when the caller has no laid-out view.
|
||||
let resolvedSafeAreaInsets = RDEPUBSafeArea.resolve(safeAreaInsets)
|
||||
let safeInsets = UIEdgeInsets(
|
||||
top: max(reflowableContentInsets.top, safeAreaInsets.top),
|
||||
left: max(reflowableContentInsets.left, safeAreaInsets.left),
|
||||
bottom: max(reflowableContentInsets.bottom, safeAreaInsets.bottom),
|
||||
right: max(reflowableContentInsets.right, safeAreaInsets.right)
|
||||
top: max(reflowableContentInsets.top, resolvedSafeAreaInsets.top),
|
||||
left: max(reflowableContentInsets.left, resolvedSafeAreaInsets.left),
|
||||
bottom: max(reflowableContentInsets.bottom, resolvedSafeAreaInsets.bottom),
|
||||
right: max(reflowableContentInsets.right, resolvedSafeAreaInsets.right)
|
||||
)
|
||||
return RDEPUBPreferences(
|
||||
fontSize: fontSize,
|
||||
|
||||
@@ -126,6 +126,19 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
||||
return spinner
|
||||
}()
|
||||
|
||||
#if DEBUG
|
||||
/// Debug-only outline of the readable content area (bounds inset by contentInsets).
|
||||
private let debugContentAreaBorderView: UIView = {
|
||||
let view = UIView()
|
||||
view.isUserInteractionEnabled = false
|
||||
view.backgroundColor = .clear
|
||||
view.layer.borderColor = UIColor.systemRed.withAlphaComponent(0.6).cgColor
|
||||
view.layer.borderWidth = 1
|
||||
view.accessibilityIdentifier = "epub.reader.debug.contentAreaBorder"
|
||||
return view
|
||||
}()
|
||||
#endif
|
||||
|
||||
private lazy var longPressGestureRecognizer: UILongPressGestureRecognizer = {
|
||||
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
|
||||
gesture.minimumPressDuration = 0.5
|
||||
@@ -243,6 +256,9 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
||||
addSubview(pageNumberLabel)
|
||||
addSubview(loadingSpinner)
|
||||
addSubview(selectionLoupeView)
|
||||
#if DEBUG
|
||||
addSubview(debugContentAreaBorderView)
|
||||
#endif
|
||||
addGestureRecognizer(longPressGestureRecognizer)
|
||||
addGestureRecognizer(panGestureRecognizer)
|
||||
addGestureRecognizer(tapGestureRecognizer)
|
||||
@@ -338,6 +354,9 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
||||
coverImageView.frame = bounds.inset(by: contentInsets)
|
||||
|
||||
let contentRect = bounds.inset(by: contentInsets)
|
||||
#if DEBUG
|
||||
debugContentAreaBorderView.frame = contentRect
|
||||
#endif
|
||||
let labelSize = pageNumberLabel.sizeThatFits(
|
||||
CGSize(width: contentRect.width, height: Self.pageNumberReservedHeight)
|
||||
)
|
||||
@@ -374,7 +393,7 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
||||
selectionController.clearSelection(renderView: coreTextRenderView)
|
||||
contentInsets = Self.safeContentInsets(
|
||||
configuration: configuration,
|
||||
safeAreaInsets: safeAreaInsets
|
||||
safeAreaInsets: RDEPUBSafeArea.resolve(safeAreaInsets)
|
||||
)
|
||||
backgroundColor = configuration.theme.contentBackgroundColor
|
||||
pageNumberLabel.textColor = configuration.theme.contentTextColor
|
||||
@@ -459,7 +478,7 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
||||
selectionController.clearSelection(renderView: coreTextRenderView)
|
||||
contentInsets = Self.safeContentInsets(
|
||||
configuration: configuration,
|
||||
safeAreaInsets: safeAreaInsets
|
||||
safeAreaInsets: RDEPUBSafeArea.resolve(safeAreaInsets)
|
||||
)
|
||||
backgroundColor = configuration.theme.contentBackgroundColor
|
||||
pageNumberLabel.textColor = configuration.theme.contentTextColor
|
||||
|
||||
@@ -66,6 +66,10 @@ final class RDReaderPreloadController {
|
||||
}
|
||||
|
||||
func invalidate(environment: Environment) {
|
||||
RDReaderTapDebug.log(
|
||||
"PreloadController.invalidate",
|
||||
"clearing pageCurlCached=\(pageCurlCachedViews.keys.sorted()) preloaded=\(preloadedPageViews.keys.sorted())"
|
||||
)
|
||||
pageCurlCachedViews.values.forEach { $0.removeFromSuperview() }
|
||||
preloadedPageViews.values.forEach { $0.removeFromSuperview() }
|
||||
pageCurlCachedViews.removeAll()
|
||||
@@ -81,8 +85,16 @@ final class RDReaderPreloadController {
|
||||
let view: UIView
|
||||
if let reusableView = detachedReusablePageView(for: pageNum) {
|
||||
view = reusableView
|
||||
RDReaderTapDebug.log(
|
||||
"PreloadController.pageViewForDisplay",
|
||||
"cache HIT page=\(pageNum) view=\(RDReaderTapDebug.describe(reusableView))"
|
||||
)
|
||||
} else {
|
||||
view = contentViewProvider(pageNum, nil) ?? UIView()
|
||||
RDReaderTapDebug.log(
|
||||
"PreloadController.pageViewForDisplay",
|
||||
"cache MISS page=\(pageNum) created=\(RDReaderTapDebug.describe(view))"
|
||||
)
|
||||
}
|
||||
if shouldCache(view: view, for: pageNum, environment: environment) {
|
||||
pageCurlCachedViews[pageNum] = view
|
||||
@@ -95,6 +107,10 @@ final class RDReaderPreloadController {
|
||||
func takePreloadedView(for pageNum: Int) -> UIView? {
|
||||
let preloaded = preloadedPageViews.removeValue(forKey: pageNum)
|
||||
preloaded?.removeFromSuperview()
|
||||
RDReaderTapDebug.log(
|
||||
"PreloadController.takePreloadedView",
|
||||
"cache \(preloaded == nil ? "MISS" : "HIT") page=\(pageNum) view=\(RDReaderTapDebug.describe(preloaded))"
|
||||
)
|
||||
return preloaded
|
||||
}
|
||||
|
||||
@@ -118,10 +134,22 @@ final class RDReaderPreloadController {
|
||||
let contentView: UIView
|
||||
if let existing = preloadedPageViews[targetPage], existing.superview === preloadHostView {
|
||||
contentView = existing
|
||||
RDReaderTapDebug.log(
|
||||
"PreloadController.prime",
|
||||
"cache HIT(preloaded) page=\(targetPage) view=\(RDReaderTapDebug.describe(existing))"
|
||||
)
|
||||
} else if let cached = pageCurlCachedViews.removeValue(forKey: targetPage), cached.superview == nil {
|
||||
contentView = cached
|
||||
RDReaderTapDebug.log(
|
||||
"PreloadController.prime",
|
||||
"cache HIT(pageCurl) page=\(targetPage) view=\(RDReaderTapDebug.describe(cached))"
|
||||
)
|
||||
} else {
|
||||
contentView = contentViewProvider(targetPage, nil) ?? UIView()
|
||||
RDReaderTapDebug.log(
|
||||
"PreloadController.prime",
|
||||
"cache MISS page=\(targetPage) created=\(RDReaderTapDebug.describe(contentView))"
|
||||
)
|
||||
}
|
||||
let shouldCacheContentView = shouldCache(view: contentView, for: targetPage, environment: environment)
|
||||
if shouldCacheContentView {
|
||||
|
||||
Reference in New Issue
Block a user