SDK 化收尾: - 新增 RDPDFReaderView 本地 pod(阅读容器、成品控制器、划线/注释、画笔、 目录/设置面板、主题与持久化契约),Demo 精简为 PDFKit 页面提供者 + 持久化宿主 - Podfile/工程接入 RDPDFReaderView 与 SnapKit,示例书改用 PDF 阅读器示例文件 手机横屏单页竖滑修复: - cell 高度改为宽度适配后的纸张高度(sizeForItemAt + 宿主按页提供纵横比), 纵向超出一屏的内容由外层 collectionView 连续滚动承接,不再被裁掉 - 页面内部不再开启同向嵌套的兜底竖向拖动,修复外层滚动被吞、无法滚到下一页 - 修复 applyContentSize 先评估拖动开关后写 contentSize 的顺序缺陷 - 页码按视口中心命中 cell 计算,补 didEndDragging 兜底;旋转重锚点与 transitionToPage 改用真实布局位置 - 横竖屏同为竖滑时也重建 cell 并刷新全部可见页的适配配置 - 横屏单页点击不再跳页,只显隐工具栏;翻页交给连续竖滑 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
151 lines
5.6 KiB
Swift
151 lines
5.6 KiB
Swift
import UIKit
|
|
|
|
public protocol RDPDFReaderKitViewDataSource: AnyObject {
|
|
func numberOfPages(in readerView: RDPDFReaderKitView) -> Int
|
|
func readerView(_ readerView: RDPDFReaderKitView, imageForPageAt index: Int) -> UIImage?
|
|
}
|
|
|
|
public protocol RDPDFReaderKitViewDelegate: AnyObject {
|
|
func readerView(_ readerView: RDPDFReaderKitView, didMoveToPage index: Int)
|
|
}
|
|
|
|
/// 独立 PDF 阅读容器:仅处理分页和页面缩放,不依赖主工程的下载/缓存/数据库。
|
|
public final class RDPDFReaderKitView: UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
|
|
public weak var dataSource: RDPDFReaderKitViewDataSource?
|
|
public weak var delegate: RDPDFReaderKitViewDelegate?
|
|
public private(set) var currentPage = 0
|
|
|
|
/// 业务层可把画笔叠加到 `RDPDFZoomablePageView.contentView`;此状态禁止单指翻页,
|
|
/// 同时保留页面的双指缩放和双指拖动。
|
|
public var isDrawingMode = false {
|
|
didSet { updatePagingInteraction() }
|
|
}
|
|
|
|
public var onConfigurePage: ((RDPDFZoomablePageView, Int) -> Void)?
|
|
|
|
private let layout: UICollectionViewFlowLayout = {
|
|
let layout = UICollectionViewFlowLayout()
|
|
layout.scrollDirection = .horizontal
|
|
layout.minimumLineSpacing = 0
|
|
layout.minimumInteritemSpacing = 0
|
|
return layout
|
|
}()
|
|
private lazy var collectionView: UICollectionView = {
|
|
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
|
view.backgroundColor = UIColor(white: 0.93, alpha: 1)
|
|
view.isPagingEnabled = true
|
|
view.showsHorizontalScrollIndicator = false
|
|
view.dataSource = self
|
|
view.delegate = self
|
|
view.panGestureRecognizer.maximumNumberOfTouches = 1
|
|
view.register(PageCell.self, forCellWithReuseIdentifier: PageCell.reuseIdentifier)
|
|
return view
|
|
}()
|
|
private var isCurrentPageZoomed = false
|
|
|
|
public override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
addSubview(collectionView)
|
|
}
|
|
|
|
public required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
addSubview(collectionView)
|
|
}
|
|
|
|
public override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
collectionView.frame = bounds
|
|
layout.itemSize = bounds.size
|
|
}
|
|
|
|
public func reloadData() {
|
|
let pageCount = dataSource?.numberOfPages(in: self) ?? 0
|
|
currentPage = max(0, min(currentPage, max(0, pageCount - 1)))
|
|
isCurrentPageZoomed = false
|
|
collectionView.reloadData()
|
|
collectionView.layoutIfNeeded()
|
|
collectionView.setContentOffset(CGPoint(x: CGFloat(currentPage) * bounds.width, y: 0), animated: false)
|
|
updatePagingInteraction()
|
|
}
|
|
|
|
public func go(to page: Int, animated: Bool = true) {
|
|
let count = dataSource?.numberOfPages(in: self) ?? 0
|
|
guard count > 0 else { return }
|
|
let target = max(0, min(page, count - 1))
|
|
guard !isDrawingMode, !isCurrentPageZoomed else { return }
|
|
currentPage = target
|
|
collectionView.setContentOffset(CGPoint(x: CGFloat(target) * bounds.width, y: 0), animated: animated)
|
|
delegate?.readerView(self, didMoveToPage: target)
|
|
}
|
|
|
|
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
dataSource?.numberOfPages(in: self) ?? 0
|
|
}
|
|
|
|
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PageCell.reuseIdentifier, for: indexPath) as! PageCell
|
|
let pageView = cell.pageView
|
|
pageView.image = dataSource?.readerView(self, imageForPageAt: indexPath.item)
|
|
pageView.isDrawingMode = isDrawingMode
|
|
pageView.zoomStateChanged = { [weak self, weak pageView] zoomed in
|
|
guard let self, let pageView, pageView.tag == self.currentPage else { return }
|
|
self.isCurrentPageZoomed = zoomed
|
|
self.updatePagingInteraction()
|
|
}
|
|
pageView.tag = indexPath.item
|
|
onConfigurePage?(pageView, indexPath.item)
|
|
return cell
|
|
}
|
|
|
|
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
updateCurrentPageFromOffset()
|
|
}
|
|
|
|
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
|
updateCurrentPageFromOffset()
|
|
}
|
|
|
|
private func updateCurrentPageFromOffset() {
|
|
guard bounds.width > 0 else { return }
|
|
let page = Int((collectionView.contentOffset.x / bounds.width).rounded())
|
|
guard page != currentPage else { return }
|
|
currentPage = page
|
|
isCurrentPageZoomed = false
|
|
updatePagingInteraction()
|
|
delegate?.readerView(self, didMoveToPage: page)
|
|
}
|
|
|
|
private func updatePagingInteraction() {
|
|
collectionView.isScrollEnabled = !isDrawingMode && !isCurrentPageZoomed
|
|
collectionView.visibleCells
|
|
.compactMap { ($0 as? PageCell)?.pageView }
|
|
.forEach { $0.isDrawingMode = isDrawingMode }
|
|
}
|
|
}
|
|
|
|
private final class PageCell: UICollectionViewCell {
|
|
static let reuseIdentifier = "RDPDFReaderKitView.PageCell"
|
|
let pageView = RDPDFZoomablePageView()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
contentView.addSubview(pageView)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
pageView.frame = contentView.bounds
|
|
}
|
|
|
|
override func prepareForReuse() {
|
|
super.prepareForReuse()
|
|
pageView.resetZoom()
|
|
pageView.zoomStateChanged = nil
|
|
}
|
|
}
|