refactor: 添加中文注释 + 优化模块结构
- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级) - 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行) - 根目录翻页容器文件移入 ReaderView/ 目录 - Resources/ 移入 EPUBCore/Resources/(与使用者归属一致) - RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖) - RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层) - 更新 podspec 资源路径
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
/// 自定义流式布局,支持阅读器的三种滚动布局模式:
|
||||
/// - horizontalScroll:水平滚动,每屏1项(竖屏)或2项(横屏双页),支持分页
|
||||
/// - 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
|
||||
|
||||
/// 每屏显示的页数
|
||||
/// 仅在水平滚动+横屏双页模式下返回2,其他情况返回1
|
||||
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
Reference in New Issue
Block a user