import UIKit /// 视口监测器,监听视图布局和屏幕旋转等视口变化事件, /// 检测变化是否显著,必要时触发重新分页或内容重建。 final class RDEPUBReaderViewportMonitor { private unowned let context: RDEPUBReaderContext private var lastAppliedViewportSignature: RDEPUBViewportSignature? private var pendingViewportChangeReason: RDEPUBViewportChangeReason? private var pendingPresentationRestoreLocation: RDEPUBLocation? private var isWaitingForViewportTransitionCompletion = false init(context: RDEPUBReaderContext) { self.context = context } private var controller: RDEPUBReaderController? { context.controller } /// 视图布局完成后检查视口是否发生变化,首次布局时触发初始加载 func viewDidLayoutSubviews() { guard let controller else { return } guard let viewportSignature = currentViewportSignature() else { return } if !controller.didStartInitialLoad { lastAppliedViewportSignature = viewportSignature controller.startInitialLoadIfNeeded() return } guard controller.publication != nil || controller.isExternalTextBook else { lastAppliedViewportSignature = viewportSignature return } guard !isWaitingForViewportTransitionCompletion else { return } handleViewportChangeIfNeeded(reason: .viewLayout, viewportSignature: viewportSignature) } /// 屏幕旋转前捕获当前阅读位置,旋转完成后检测视口变化并处理 /// - Parameter coordinator: 转场协调器 func viewWillTransition(with coordinator: UIViewControllerTransitionCoordinator) { guard let controller else { return } guard controller.didStartInitialLoad else { return } pendingPresentationRestoreLocation = controller.currentVisibleLocation() ?? controller.persistenceLocation() isWaitingForViewportTransitionCompletion = true coordinator.animate(alongsideTransition: nil) { [weak self] _ in guard let self, let controller = self.controller else { return } self.isWaitingForViewportTransitionCompletion = false controller.view.layoutIfNeeded() self.handleViewportChangeIfNeeded(reason: .orientationTransition) } } /// 重置所有视口状态,用于重新加载书籍 func resetForReload() { lastAppliedViewportSignature = currentViewportSignature() pendingViewportChangeReason = nil pendingPresentationRestoreLocation = nil isWaitingForViewportTransitionCompletion = false } /// 消费并返回待恢复的阅读位置(一次性读取后清空) func consumePendingPresentationRestoreLocation() -> RDEPUBLocation? { defer { pendingPresentationRestoreLocation = nil } return pendingPresentationRestoreLocation } /// 主动捕获当前阅读位置到待恢复队列,供后续视口变化后恢复使用 func capturePendingPresentationRestoreLocation() { guard let controller else { return } pendingPresentationRestoreLocation = controller.currentVisibleLocation() ?? controller.persistenceLocation() } /// 分页完成后处理挂起的视口变化(如有),避免分页过程中重复触发 func processPendingChangeAfterPagination() { guard let pendingReason = pendingViewportChangeReason else { return } pendingViewportChangeReason = nil DispatchQueue.main.async { [weak self] in self?.handleViewportChangeIfNeeded(reason: pendingReason) } } /// 获取当前视口签名,包含容器尺寸和安全区域信息 func currentViewportSignature() -> RDEPUBViewportSignature? { guard let controller else { return nil } let containerSize = controller.readerView.bounds.size == .zero ? controller.view.bounds.size : controller.readerView.bounds.size guard containerSize.width > 0, containerSize.height > 0 else { return nil } let insets = controller.view.safeAreaInsets return RDEPUBViewportSignature( width: containerSize.width, height: containerSize.height, safeTop: insets.top, safeLeft: insets.left, safeBottom: insets.bottom, safeRight: insets.right ) } /// 检测视口签名是否发生显著变化,若变化则触发重新分页或重建外部 TextBook func handleViewportChangeIfNeeded( reason: RDEPUBViewportChangeReason, viewportSignature: RDEPUBViewportSignature? = nil ) { guard let controller else { return } guard controller.didStartInitialLoad, let signature = viewportSignature ?? currentViewportSignature() else { return } if controller.isRepaginating { pendingViewportChangeReason = reason return } if let lastAppliedViewportSignature, !signature.differsSignificantly(from: lastAppliedViewportSignature) { return } lastAppliedViewportSignature = signature if controller.isExternalTextBook { controller.rebuildExternalTextBook() return } guard controller.publication != nil else { return } controller.repaginatePreservingCurrentLocation() } }