refactor: 添加中文注释 + 优化模块结构

- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级)
- 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行)
- 根目录翻页容器文件移入 ReaderView/ 目录
- Resources/ 移入 EPUBCore/Resources/(与使用者归属一致)
- RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖)
- RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层)
- 更新 podspec 资源路径
This commit is contained in:
shen
2026-05-25 10:19:14 +08:00
parent 5af90c1148
commit 54798ba578
88 changed files with 2492 additions and 4469 deletions
@@ -0,0 +1,55 @@
//
// RDReaderContentCell.swift
// RDReaderDemo
//
// Created by yangsq on 2021/8/7.
//
// UICollectionViewCell 宿
// cell containerView
// RDReaderDataSource.pageContentView() containerView
//
// RDReaderView cell UICollectionView 使
//
import UIKit
/// cell 宿
/// UICollectionView
/// ``containerView`` layoutSubviews
/// frame contentView
class RDReaderContentCell: UICollectionViewCell {
///
private var _containerView: UIView? = nil
///
/// contentView
///
var containerView: UIView? {
set {
guard newValue !== _containerView else { return }
_containerView?.removeFromSuperview()
_containerView = newValue
if let containerView = _containerView {
contentView.addSubview(containerView)
setNeedsLayout()
}
}
get {
return _containerView
}
}
///
/// containerView frame contentView
override func layoutSubviews() {
super.layoutSubviews()
_containerView?.frame = CGRect(x: 0, y: 0, width: contentView.frame.width, height: contentView.frame.height)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,375 @@
//
// RDReaderFlowLayout.swift
// RDReaderDemo
//
// Created by yangsq on 2021/8/6.
//
// UICollectionViewFlowLayout
// item +
//
//
// RDReaderView RDReaderView
//
import UIKit
///
///
public protocol RDReaderFlowLayoutDataSoure: NSObjectProtocol {
///
/// - Parameters:
/// - flowLayout:
/// - pageIndex:
/// - Returns: nil 使collectionView.frame.height
func heigtOfVerticalScrollPage(flowLayout: RDReaderFlowLayout, pageIndex: Int) -> CGFloat?
}
///
///
@objc public protocol RDReaderFlowLayoutDelegate: NSObjectProtocol {
///
/// - Parameters:
/// - flowLayout:
/// - pageIndex:
func pageNum(flowLayout: RDReaderFlowLayout, pageIndex: Int)
}
///
/// - horizontalScroll12
/// - verticalScroll
///
/// ``displayType``
/// ``isLandscapeDualPage`` ``coverPageIndex``
public class RDReaderFlowLayout: UICollectionViewFlowLayout {
/// 使
var displayType: RDReaderView.DisplayType = .horizontalScroll {
didSet {
invalidateLayout()
}
}
///
///
var isLandscapeDualPage: Bool = false {
didSet {
if oldValue != isLandscapeDualPage {
invalidateLayout()
}
}
}
///
///
var coverPageIndex: Int? = nil {
didSet {
if oldValue != coverPageIndex {
invalidateLayout()
}
}
}
/// prepare bounds
private var lastPreparedBoundsSize: CGSize = .zero
///
/// +21
var pagesPerScreen: Int {
guard isLandscapeDualPage else { return 1 }
switch displayType {
case .horizontalScroll:
if let cv = collectionView, cv.bounds.width > cv.bounds.height {
return 2
}
return 1
default:
return 1
}
}
///
weak var dataSource: RDReaderFlowLayoutDataSoure? = nil
///
weak var delegate: RDReaderFlowLayoutDelegate? = nil
///
private var hasCoverPageInDualMode: Bool {
return pagesPerScreen > 1 && coverPageIndex != nil
}
/// item frame
///
/// - Parameters:
/// - index: item
/// - screenWidth:
/// - halfWidth:
/// - height:
/// - Returns: item frame
private func coverAwareFrame(for index: Int, screenWidth: CGFloat, halfWidth: CGFloat, height: CGFloat) -> CGRect {
guard let coverIndex = coverPageIndex else {
let pairIdx = index / 2
let side = index % 2
let x = CGFloat(pairIdx) * screenWidth + CGFloat(side) * halfWidth
return CGRect(x: x, y: 0, width: halfWidth, height: height)
}
if index == coverIndex {
let screensBeforeCover = coverIndex / 2
let x = CGFloat(screensBeforeCover) * screenWidth
return CGRect(x: x, y: 0, width: screenWidth, height: height)
}
let adjustedIndex = index - (coverIndex + 1)
let pairIdx = adjustedIndex / 2
let side = adjustedIndex % 2
let coverScreens = coverIndex / 2 + 1
let x = CGFloat(coverScreens + pairIdx) * screenWidth + CGFloat(side) * halfWidth
return CGRect(x: x, y: 0, width: halfWidth, height: height)
}
/// item
/// - Parameters:
/// - offset:
/// - screenWidth:
/// - Returns: item
private func coverAwareStartIndex(for offset: CGFloat, screenWidth: CGFloat) -> Int {
let pps = pagesPerScreen
guard let coverIdx = coverPageIndex, hasCoverPageInDualMode else {
let screenIndex = Int((offset / screenWidth).rounded(.down))
return screenIndex * pps
}
let coverScreens = coverIdx / 2 + 1
let coverEnd = CGFloat(coverScreens) * screenWidth
if offset < coverEnd {
let screenIdx = Int((offset / screenWidth).rounded(.down))
return screenIdx >= coverIdx / 2 ? coverIdx : screenIdx * pps
} else {
let pairIdx = Int(((offset - coverEnd) / screenWidth).rounded(.down))
return coverIdx + 1 + pairIdx * 2
}
}
/// delegate
private var currentPage: Int = 0 {
didSet {
if let delegate = delegate, delegate.responds(to: #selector(RDReaderFlowLayoutDelegate.pageNum(flowLayout:pageIndex:))), currentPage != oldValue, currentPage >= 0 {
delegate.pageNum(flowLayout: self, pageIndex: currentPage)
}
}
}
///
/// - Parameter displayType:
init(displayType: RDReaderView.DisplayType) {
self.displayType = displayType
super.init()
}
/// item
public override func prepare() {
super.prepare()
guard let collectionView = self.collectionView else {
return
}
guard collectionView.frame.width > 0, collectionView.frame.height > 0 else {
return
}
lastPreparedBoundsSize = collectionView.bounds.size
if #available(iOS 11.0, *) {
collectionView.contentInsetAdjustmentBehavior = .never
} else {
collectionView.ss_superViewController?.automaticallyAdjustsScrollViewInsets = false
}
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
minimumLineSpacing = 0
minimumInteritemSpacing = 0
switch self.displayType {
case .horizontalScroll:
scrollDirection = .horizontal
let columns = CGFloat(pagesPerScreen)
itemSize = CGSize(width: collectionView.frame.width / columns, height: collectionView.frame.height)
collectionView.isPagingEnabled = true
case .verticalScroll:
itemSize = CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
scrollDirection = .vertical
collectionView.isPagingEnabled = false
collectionView.showsVerticalScrollIndicator = true
default: break
}
}
/// bounds 使
/// true true
public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if newBounds.size != lastPreparedBoundsSize {
return true
}
switch displayType {
case .verticalScroll:
return true
case .horizontalScroll:
return true
default:
return false
}
}
/// collectionView
///
/// +
public override var collectionViewContentSize: CGSize {
var size = super.collectionViewContentSize
guard let collectionView = collectionView else {
return size
}
if displayType == .verticalScroll {
let totalHeight = [Int](0..<collectionView.numberOfItems(inSection: 0)).reduce(0.0, { result, num in
result + (dataSource?.heigtOfVerticalScrollPage(flowLayout: self, pageIndex: num) ?? collectionView.frame.height)
})
size.height = totalHeight
}
if hasCoverPageInDualMode, displayType == .horizontalScroll {
let totalItems = collectionView.numberOfItems(inSection: 0)
let screenWidth = collectionView.frame.width
let remainingItems = max(0, totalItems - 1)
let pairedScreens = (remainingItems + 1) / 2
size.width = screenWidth * CGFloat(1 + pairedScreens)
}
return size
}
///
/// item
/// cell
public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = super.layoutAttributesForElements(in: rect)
guard let collectionView = collectionView else {
return attributes
}
if self.displayType == .verticalScroll {
let rowCount = collectionView.numberOfItems(inSection: 0)
var totalHeight: CGFloat = 0
for index in 0..<rowCount {
totalHeight += dataSource?.heigtOfVerticalScrollPage(flowLayout: self, pageIndex: index) ?? collectionView.frame.height
if totalHeight > collectionView.contentOffset.y {
self.currentPage = index
break
}
}
(attributes ?? []).forEach({ attr in
let indexPath = attr.indexPath
let lastRow = max(0, indexPath.row - 1)
let height = dataSource?.heigtOfVerticalScrollPage(flowLayout: self, pageIndex: indexPath.row) ?? collectionView.frame.height
var lastTotalHeight: CGFloat = 0
if indexPath.row > 0 {
lastTotalHeight = [Int](0...lastRow).reduce(0.0) { result, row in
result + (dataSource?.heigtOfVerticalScrollPage(flowLayout: self, pageIndex: row) ?? collectionView.frame.height)
}
}
if let cell = collectionView.cellForItem(at: indexPath) {
cell.layer.shadowOpacity = 0
}
attr.frame = CGRect(x: 0, y: lastTotalHeight, width: collectionView.frame.width, height: height)
})
}
if self.displayType == .horizontalScroll {
let pps = pagesPerScreen
if hasCoverPageInDualMode {
let screenWidth = collectionView.frame.width
let halfWidth = screenWidth / 2.0
let rows = collectionView.numberOfItems(inSection: 0)
self.currentPage = coverAwareStartIndex(for: collectionView.contentOffset.x, screenWidth: screenWidth)
var attrs = [UICollectionViewLayoutAttributes]()
for index in 0..<rows {
let frame = coverAwareFrame(for: index, screenWidth: screenWidth, halfWidth: halfWidth, height: collectionView.frame.height)
guard frame.intersects(rect) else { continue }
let indexPath = IndexPath(item: index, section: 0)
let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attr.frame = frame
attrs.append(attr)
if let cell = collectionView.cellForItem(at: indexPath) {
cell.layer.shadowOpacity = 0
}
}
attributes = attrs
} else if pps > 1 {
let currentPage = Int((collectionView.contentOffset.x / collectionView.frame.width).rounded(.down)) * pps
self.currentPage = currentPage
(attributes ?? []).forEach({ attr in
if let cell = collectionView.cellForItem(at: attr.indexPath) {
cell.layer.shadowOpacity = 0
}
})
} else {
let currentPage = Int((collectionView.contentOffset.x / collectionView.frame.width).rounded(.down))
self.currentPage = currentPage
(attributes ?? []).forEach({ attr in
if let cell = collectionView.cellForItem(at: attr.indexPath) {
cell.layer.shadowOpacity = 0
}
})
}
}
return attributes
}
/// contentOffset
///
/// - Parameter count:
/// - Returns: contentOffset
func currentContentOffset(count: Int) -> CGPoint {
guard let collectionView = collectionView else {
return .zero
}
guard collectionView.frame.width > 0, collectionView.frame.height > 0 else {
return .zero
}
let safeCount = max(0, count)
switch displayType {
case .verticalScroll:
guard safeCount > 0 else { return .zero }
let totalHeight = [Int](0...(safeCount - 1)).reduce(0.0) { result, pageNum in
result + (dataSource?.heigtOfVerticalScrollPage(flowLayout: self, pageIndex: pageNum) ?? collectionView.frame.height)
}
return CGPoint(x: 0, y: totalHeight)
default:
let pps = pagesPerScreen
let screenWidth = collectionView.frame.width
if hasCoverPageInDualMode, let coverIdx = coverPageIndex {
if safeCount == coverIdx {
let screensBeforeCover = coverIdx / 2
return CGPoint(x: CGFloat(screensBeforeCover) * screenWidth, y: 0)
}
if safeCount < coverIdx {
return CGPoint(x: CGFloat(safeCount / 2) * screenWidth, y: 0)
}
let coverScreens = coverIdx / 2 + 1
let adjustedIndex = safeCount - (coverIdx + 1)
let pairIndex = adjustedIndex / 2
return CGPoint(x: CGFloat(coverScreens + pairIndex) * screenWidth, y: 0)
} else if pps > 1 {
let screenIndex = safeCount / pps
return CGPoint(x: CGFloat(screenIndex) * screenWidth, y: 0)
} else {
return CGPoint(x: CGFloat(safeCount) * itemSize.width, y: 0)
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,58 @@
//
// RDReaderGestureController.swift
// RDReaderDemo
//
// Created by yangsq on 2021/8/10.
//
//
//
//
// RDReaderView tapGestureRecognizer
//
// RDReaderView
//
import UIKit
///
///
/// RDReaderView 使
class RDReaderGestureController: UIViewController {
///
var topToolView: UIView?
///
var bottomToolView: UIView?
///
/// - Parameters:
/// - topToolView:
/// - bottomToolView:
init(topToolView: UIView?, bottomToolView: UIView?) {
self.topToolView = topToolView
self.bottomToolView = bottomToolView
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
@@ -0,0 +1,89 @@
//
// RDReaderPageChildViewController.swift
// RDReaderDemo
//
// Created by yangsq on 2021/8/9.
//
// UIPageViewController
// pageCurl 仿 RDReaderPageChildViewController
// contentContainerView
//
// RDReaderView UIPageViewController 使
//
import UIKit
/// 仿
/// UIPageViewController
/// ``contentView`` ``pageNum``
/// contentContainerView
class RDReaderPageChildViewController: UIViewController {
/// contentView
private let contentContainerView = UIView()
///
/// contentContainerView
var contentView: UIView? {
didSet {
guard isViewLoaded else { return }
installContentView()
}
}
///
var pageNum: Int = 0
///
/// - Parameters:
/// - contentView:
/// - pageNum: 0
init(contentView: UIView?, pageNum: Int = 0) {
self.contentView = contentView
self.pageNum = pageNum
super.init(nibName: nil, bundle: nil)
}
///
/// contentContainerView
override func loadView() {
view = UIView()
view.backgroundColor = .clear
contentContainerView.frame = view.bounds
contentContainerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentContainerView.backgroundColor = .clear
view.addSubview(contentContainerView)
installContentView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
/// contentContainerView
/// contentContainerView contentView
private func installContentView() {
contentContainerView.subviews.forEach { $0.removeFromSuperview() }
guard let contentView else { return }
contentView.removeFromSuperview()
contentView.frame = contentContainerView.bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentContainerView.addSubview(contentView)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
File diff suppressed because it is too large Load Diff