import UIKit /// EPUB 阅读器界面组装协调器:负责阅读器初次启动时的 UI 搭建。 /// /// 职责: /// - 组装阅读器视图层次结构(readerView、loadingIndicator、errorLabel) /// - 注册内容视图类型 /// - 处理外部纯文本图书的启动收尾逻辑 final class RDEPUBReaderAssemblyCoordinator { private unowned let context: RDEPUBReaderContext init(context: RDEPUBReaderContext) { self.context = context } /// 组装阅读器界面:添加 readerView、loadingIndicator、errorLabel 到控制器视图,并配置顶部工具栏。 func assembleInterface() { guard let controller = context.controller, let readerView = context.readerView else { return } controller.view.backgroundColor = context.configuration.theme.contentBackgroundColor setupReaderView(readerView, in: controller.view) setupLoadingIndicator(controller.loadingIndicator, in: controller.view) setupErrorLabel(controller.errorLabel, in: controller.view) controller.delegate?.epubReader(controller, configureTopToolView: controller.topToolView) #if DEBUG print("[ReadViewDemo] assembleInterface: dataSource=\(readerView.dataSource != nil ? "set" : "nil"), numberOfPages=\(readerView.numberOfPages())") #endif } /// 外部纯文本图书启动时,加载已保存的书签、高亮和阅读位置,完成分页收尾。 func finishExternalTextBookLaunchIfNeeded() { guard let runtime = context.runtime, let controller = context.controller, context.isExternalTextBook else { return } let restoreLocation = context.currentBookIdentifier.flatMap { context.persistence?.loadLocation(for: $0) } if let id = context.currentBookIdentifier { context.activeBookmarks = context.persistence?.loadBookmarks(for: id) ?? [] context.activeHighlights = context.persistence?.loadHighlights(for: id) ?? [] } if let textBook = controller.textBook { #if DEBUG print("[ReadViewDemo] finishExternalTextBook: applying textBook with \(textBook.pages.count) pages") #endif runtime.applyTextBook(textBook, restoreLocation: restoreLocation) #if DEBUG print("[ReadViewDemo] finishExternalTextBook: after applyTextBook, numberOfPages=\(context.readerView?.numberOfPages() ?? -1)") #endif } else { runtime.finishPagination(restoreLocation: restoreLocation) } } private func setupReaderView(_ readerView: RDReaderView, in containerView: UIView) { readerView.dataSource = context.controller readerView.delegate = context.controller readerView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(readerView) NSLayoutConstraint.activate([ readerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), readerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), readerView.topAnchor.constraint(equalTo: containerView.topAnchor), readerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) ]) readerView.register(contentView: RDEPUBTextContentView.self, contentViewWithReuseIdentifier: NSStringFromClass(RDEPUBTextContentView.self)) readerView.register(contentView: RDEPUBWebContentView.self, contentViewWithReuseIdentifier: NSStringFromClass(RDEPUBWebContentView.self)) context.controller?.applyReaderViewConfiguration() } private func setupLoadingIndicator(_ loadingIndicator: UIActivityIndicatorView, in containerView: UIView) { loadingIndicator.hidesWhenStopped = true loadingIndicator.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(loadingIndicator) NSLayoutConstraint.activate([ loadingIndicator.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), loadingIndicator.centerYAnchor.constraint(equalTo: containerView.centerYAnchor) ]) } private func setupErrorLabel(_ errorLabel: UILabel, in containerView: UIView) { errorLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(errorLabel) NSLayoutConstraint.activate([ errorLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 24), errorLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -24), errorLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor) ]) } }