Files
ReadViewSDK/Sources/RDReaderView/RDReaderView.swift
T
2026-05-21 19:40:51 +08:00

753 lines
31 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// RDReaderView.swift
// RDReaderDemo
//
// Created by yangsq on 2021/8/6.
//
import UIKit
@objc public protocol RDReaderDataSource: NSObjectProtocol {
func pageCountOfReaderView(readerView: RDReaderView) -> Int
func pageContentView(readerView: RDReaderView, pageNum: Int, containerView: UIView?) -> UIView
func pageIdentifier(readerView: RDReaderView, pageNum: Int) -> String?
@objc optional func topToolView(readerView: RDReaderView) -> UIView?
@objc optional func bottomToolView(readerView: RDReaderView) -> UIView?
}
@objc public protocol RDReaderDelegate: NSObjectProtocol {
func pageNum(readerView: RDReaderView, pageNum: Int)
/// 横竖屏切换时回调,可在此重新分页
@objc optional func readerViewOrientationWillChange(readerView: RDReaderView, isLandscape: Bool)
}
extension RDReaderView {
public enum DisplayType {
case pageCurl
case horizontalScroll
case verticalScroll
}
/// 翻页方向
public enum PageDirection {
/// 从左往右翻页(默认,适用于中文/英文书籍)
case leftToRight
/// 从右往左翻页(适用于日文漫画等)
case rightToLeft
}
}
public class RDReaderView: UIView {
enum TapEvent {
case none, left, center, right
}
private lazy var pageViewController: UIPageViewController = {
let pageVC = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil)
pageVC.delegate = self
pageVC.dataSource = self
return pageVC
}()
private lazy var layout: RDReaderFlowLayout = {
let layout = RDReaderFlowLayout(displayType: .horizontalScroll)
layout.dataSource = self
layout.delegate = self
return layout
}()
private lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.clear
return collectionView
}()
public var currentPage: Int = -1 {
didSet {
if let delegate = delegate, currentPage != oldValue, delegate.responds(to: #selector(RDReaderDelegate.pageNum(readerView:pageNum:))) {
delegate.pageNum(readerView: self, pageNum: currentPage)
}
}
}
private(set) lazy var tapGestureRecognizer: UITapGestureRecognizer = {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(tap:)))
return tap
}()
private var tapEvent: TapEvent = .none {
didSet {
let isRTL = pageDirection == .rightToLeft
switch tapEvent {
case .left:
if currentDisplayType != .pageCurl {
if isRTL { goNextPage() } else { goPreviousPage() }
}
case .right:
if currentDisplayType != .pageCurl {
if isRTL { goPreviousPage() } else { goNextPage() }
}
case .center:
tapCenter()
default:
break
}
}
}
private func goNextPage() {
let totalPages = dataSource?.pageCountOfReaderView(readerView: self) ?? 0
if pagesPerScreen > 1 {
if let target = adjacentDualPage(from: currentPage, forward: true) {
transitionToPage(pageNum: target, animated: true)
}
} else if currentPage + 1 < totalPages {
transitionToPage(pageNum: currentPage + 1, animated: true)
}
}
private func goPreviousPage() {
if pagesPerScreen > 1 {
if let target = adjacentDualPage(from: currentPage, forward: false) {
transitionToPage(pageNum: target, animated: true)
}
} else if currentPage - 1 >= 0 {
transitionToPage(pageNum: currentPage - 1, animated: true)
}
}
public weak var dataSource: RDReaderDataSource? = nil
public weak var delegate: RDReaderDelegate? = nil
public var currentDisplayType: RDReaderView.DisplayType = .pageCurl
public var toolViewAnimationDuration: TimeInterval = 0.3
/// 是否启用横屏双页显示
public var landscapeDualPageEnabled: Bool = false
/// 翻页方向,默认从左往右(.leftToRight
public var pageDirection: RDReaderView.PageDirection = .leftToRight
/// 横屏双页模式下,封面页的索引。设置后该页在横屏时独占一屏,后续页面两两配对。
/// 设为 nil 表示没有封面页(所有页面正常两两配对)。
public var coverPageIndex: Int? = nil
/// 是否启用了封面页独占
private var hasCoverPage: Bool {
return coverPageIndex != nil
}
/// 当前是否横屏
private var isLandscape: Bool {
return bounds.width > bounds.height
}
/// 每屏显示的页数
public var pagesPerScreen: Int {
if !landscapeDualPageEnabled { return 1 }
if currentDisplayType == .verticalScroll { return 1 }
return isLandscape ? 2 : 1
}
/// 判断某页在横屏双页模式下是否独占一屏(封面页)
/// - Parameter pageNum: 页码
/// - Returns: 是否独占一屏
public func isFullScreenPage(_ pageNum: Int) -> Bool {
guard landscapeDualPageEnabled, isLandscape, let coverIndex = coverPageIndex else { return false }
return pageNum == coverIndex
}
/// 根据逻辑页码计算横屏双页模式下的配对信息
/// 封面页独占一屏,后续页面两两配对
/// - Parameter pageNum: 逻辑页码
/// - Returns: (左页码, 右页码(可选))
private func dualPagePair(for pageNum: Int) -> (left: Int, right: Int?) {
let totalPages = dataSource?.pageCountOfReaderView(readerView: self) ?? 0
if let coverIndex = coverPageIndex {
if pageNum == coverIndex {
// 封面页独占一屏
return (coverIndex, nil)
}
// 封面之后的页面:偏移1后两两配对
// 例如封面是0页,则 1+2, 3+4, 5+6...
let adjustedIndex = pageNum - (coverIndex + 1) // 跳过封面后的偏移
let pairStart = coverIndex + 1 + (adjustedIndex / 2) * 2
let left = pairStart
let right = pairStart + 1 < totalPages ? pairStart + 1 : nil
return (left, right)
} else {
// 无封面页:正常两两配对 0+1, 2+3, 4+5...
let left = (pageNum / 2) * 2
let right = left + 1 < totalPages ? left + 1 : nil
return (left, right)
}
}
/// 计算横屏双页下,某页往前/后翻一屏后的起始页码
/// - Parameters:
/// - pageNum: 当前页码
/// - forward: 是否向后翻
/// - Returns: 目标页码,nil 表示到头了
private func adjacentDualPage(from pageNum: Int, forward: Bool) -> Int? {
let totalPages = dataSource?.pageCountOfReaderView(readerView: self) ?? 0
let pair = dualPagePair(for: pageNum)
if forward {
let nextStart = (pair.right ?? pair.left) + 1
return nextStart < totalPages ? nextStart : nil
} else {
let prevEnd = pair.left - 1
guard prevEnd >= 0 else { return nil }
return dualPagePair(for: prevEnd).left
}
}
private var previousIsLandscape: Bool?
private var contentViews = [String : UIView.Type]()
private var willPreviousTransitionToViewController: UIViewController? = nil
private var willNextTransitionToViewController: UIViewController? = nil
private var willTransitionToViewController: UIViewController? = nil
private var topToolView: UIView?
private var bottomToolView: UIView?
private var isShowToolView: Bool = false
private var isTransitioning: Bool = false
private var didBuildUI = false
/// 用于 pageCurl 双页模式下封面页旁边的空白页
static let blankPageNum = Int.max
/// 用于 pageCurl 双页模式下末尾不成对页旁边的空白页
static let blankEndPageNum = Int.max - 1
override init(frame: CGRect) {
super.init(frame: frame)
}
public override func layoutSubviews() {
super.layoutSubviews()
guard bounds.width > 0, bounds.height > 0 else { return }
let nowLandscape = isLandscape
if let prev = previousIsLandscape, prev != nowLandscape {
previousIsLandscape = nowLandscape
// 延迟到下一个 RunLoop 执行,避免在 layoutSubviews 中嵌套触发 reloadData/layoutIfNeeded
// 造成 collectionView 中间态尺寸不一致的问题
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.orientationChanged(isNowLandscape: nowLandscape)
}
} else if previousIsLandscape == nil {
previousIsLandscape = nowLandscape
layout.isLandscapeDualPage = landscapeDualPageEnabled && nowLandscape
}
}
/// 横竖屏切换处理
/// - Parameter isNowLandscape: 当前是否为横屏
private func orientationChanged(isNowLandscape: Bool) {
let savedPage = max(0, currentPage)
// 1. 通知代理方向变化。正文级重分页由上层控制器统一接管,这里只保留容器级刷新钩子。
delegate?.readerViewOrientationWillChange?(readerView: self, isLandscape: isNowLandscape)
// 2. 更新布局的横屏双页标记
layout.isLandscapeDualPage = landscapeDualPageEnabled && isNowLandscape
layout.coverPageIndex = coverPageIndex
switch currentDisplayType {
case .pageCurl:
// 仿真翻页:通过 spineLocation 原生支持双页,需要重建 PageViewController
rebuildPageViewController()
transitionToPage(pageNum: savedPage)
default:
// 滚动模式:禁用动画防止旋转过渡中出现尺寸抖动
UIView.performWithoutAnimation {
// 强制使布局完全失效并重新计算
collectionView.collectionViewLayout.invalidateLayout()
collectionView.reloadData()
collectionView.layoutIfNeeded()
// 3. 根据保存的页码恢复滚动位置
let totalPages = dataSource?.pageCountOfReaderView(readerView: self) ?? 0
let safePage = min(savedPage, max(0, totalPages - 1))
let targetOffset = layout.currentContentOffset(count: safePage)
collectionView.setContentOffset(targetOffset, animated: false)
}
}
}
/// 创建带指定 spine 位置的 UIPageViewController
/// - Parameter isDualPage: 是否双页模式(横屏时书脊在中间:.mid)
private func createPageViewController(isDualPage: Bool) -> UIPageViewController {
let options: [UIPageViewController.OptionsKey: Any]?
if isDualPage {
options = [.spineLocation: NSNumber(value: UIPageViewController.SpineLocation.mid.rawValue)]
} else {
options = nil
}
let pageVC = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: options)
pageVC.delegate = self
pageVC.dataSource = self
pageVC.isDoubleSided = isDualPage
return pageVC
}
/// 重建 UIPageViewController。横竖屏切换时需要重建,因为 spineLocation 只能在初始化时设置
private func rebuildPageViewController() {
detachPageViewControllerIfNeeded()
let isDualPage = landscapeDualPageEnabled && isLandscape
let pageVC = createPageViewController(isDualPage: isDualPage)
pageViewController = pageVC
attachPageViewControllerIfNeeded()
}
public override func didMoveToSuperview() {
super.didMoveToSuperview()
if superview != nil {
makeUI()
}
}
private func attachPageViewControllerIfNeeded() {
guard let parentViewController = self.ss_superViewController else { return }
var didAddToParent = false
if pageViewController.parent !== parentViewController {
if pageViewController.parent != nil {
pageViewController.willMove(toParent: nil)
pageViewController.view.removeFromSuperview()
pageViewController.removeFromParent()
}
parentViewController.addChild(pageViewController)
didAddToParent = true
}
pageViewController.view.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
pageViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
if pageViewController.view.superview !== self {
insertSubview(pageViewController.view, at: 0)
}
if didAddToParent {
pageViewController.didMove(toParent: parentViewController)
}
}
private func detachPageViewControllerIfNeeded() {
if pageViewController.parent != nil {
pageViewController.willMove(toParent: nil)
}
pageViewController.view.removeFromSuperview()
if pageViewController.parent != nil {
pageViewController.removeFromParent()
}
}
private func makeUI() {
guard !didBuildUI else {
if currentDisplayType == .pageCurl {
attachPageViewControllerIfNeeded()
}
return
}
didBuildUI = true
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(UICollectionViewCell.self))
collectionView.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
if currentDisplayType == .pageCurl {
attachPageViewControllerIfNeeded()
}
addGestureRecognizer(tapGestureRecognizer)
// 不取消底层触摸事件,确保工具栏按钮(返回等)的 touchUpInside 能正常触发
tapGestureRecognizer.cancelsTouchesInView = false
}
@objc private func tapAction(tap: UITapGestureRecognizer) {
let point = tap.location(in: tap.view)
if isShowToolView {
let hitView = hitTest(point, with: nil)
if let top = topToolView, isHitView(hitView, inside: top, point: point) { return }
if let bottom = bottomToolView, isHitView(hitView, inside: bottom, point: point) { return }
}
let viewFrame = tap.view!.frame
let leftFrame = CGRect(x: 0, y: 0, width: viewFrame.width / 3, height: viewFrame.height)
let centerFrame = CGRect(x: viewFrame.width / 3, y: 0, width: viewFrame.width / 3, height: viewFrame.height)
let rightFrame = CGRect(x: viewFrame.width * 2 / 3, y: 0, width: viewFrame.width / 3, height: viewFrame.height)
if leftFrame.contains(point) {
if isShowToolView {
tapEvent = .center
} else {
tapEvent = .left
}
}
if centerFrame.contains(point) {
tapEvent = .center
}
if rightFrame.contains(point) {
if isShowToolView {
tapEvent = .center
} else {
tapEvent = .right
}
}
}
private func tapCenter() {
isShowToolView = !isShowToolView
if isShowToolView {
if let topToolView = topToolView {
addSubview(topToolView)
topToolView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topToolView.leadingAnchor.constraint(equalTo: leadingAnchor),
topToolView.trailingAnchor.constraint(equalTo: trailingAnchor),
topToolView.topAnchor.constraint(equalTo: topAnchor)
])
layoutIfNeeded()
topToolView.transform = CGAffineTransform(translationX: 0, y: -topToolView.bounds.height)
UIView.animate(withDuration: toolViewAnimationDuration) {
topToolView.transform = .identity
}
}
if let bottomToolView = bottomToolView {
addSubview(bottomToolView)
bottomToolView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
bottomToolView.leadingAnchor.constraint(equalTo: leadingAnchor),
bottomToolView.trailingAnchor.constraint(equalTo: trailingAnchor),
bottomToolView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
layoutIfNeeded()
bottomToolView.transform = CGAffineTransform(translationX: 0, y: bottomToolView.bounds.height)
UIView.animate(withDuration: toolViewAnimationDuration) {
bottomToolView.transform = .identity
}
}
collectionView.isUserInteractionEnabled = false
pageViewController.view.isUserInteractionEnabled = false
} else {
if let topToolView = topToolView {
UIView.animate(withDuration: toolViewAnimationDuration, animations: {
topToolView.transform = CGAffineTransform(translationX: 0, y: -topToolView.bounds.height)
}) { _ in
topToolView.removeFromSuperview()
}
}
if let bottomToolView = bottomToolView {
UIView.animate(withDuration: toolViewAnimationDuration, animations: {
bottomToolView.transform = CGAffineTransform(translationX: 0, y: bottomToolView.bounds.height)
}) { _ in
bottomToolView.removeFromSuperview()
}
}
collectionView.isUserInteractionEnabled = true
pageViewController.view.isUserInteractionEnabled = true
}
}
private func isHitView(_ hitView: UIView?, inside toolView: UIView, point: CGPoint) -> Bool {
if toolView.frame.contains(point) {
return true
}
guard let hitView else { return false }
return hitView === toolView || hitView.isDescendant(of: toolView)
}
/// 切换翻页模式(仿真/水平滚动/上下滚动)
/// 会重建底层视图(PageViewController 或 CollectionView),并恢复到当前页
public func switchReaderDisplayType(_ displayType: RDReaderView.DisplayType) {
self.currentDisplayType = displayType
if currentPage == -1 {
currentPage = 0
}
// 同步横屏双页标记到布局
layout.isLandscapeDualPage = landscapeDualPageEnabled && isLandscape
layout.coverPageIndex = coverPageIndex
switch displayType {
case .pageCurl:
self.collectionView.removeFromSuperview()
self.collectionView.transform = .identity
attachPageViewControllerIfNeeded()
rebuildPageViewController()
transitionToPage(pageNum: currentPage)
default:
detachPageViewControllerIfNeeded()
// RTL 水平模式翻转 collectionView
if pageDirection == .rightToLeft && displayType != .verticalScroll {
collectionView.transform = CGAffineTransform(scaleX: -1, y: 1)
} else {
collectionView.transform = .identity
}
// 确保 collectionView frame 正确后再触发布局计算
collectionView.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
insertSubview(self.collectionView, at: 0)
layout.displayType = displayType
transitionToPage(pageNum: currentPage)
}
}
/// 跳转到指定页码
/// - Parameters:
/// - pageNum: 目标页码(item 索引)
/// - animated: 是否动画过渡
public func transitionToPage(pageNum: Int, animated: Bool = false) {
switch currentDisplayType {
case .pageCurl:
let isDualPage = landscapeDualPageEnabled && isLandscape
// RTL 时动画方向需要反转
let direction: UIPageViewController.NavigationDirection
if pageDirection == .rightToLeft {
direction = pageNum > currentPage ? .reverse : .forward
} else {
direction = pageNum > currentPage ? .forward : .reverse
}
if isDualPage {
let pair = dualPagePair(for: pageNum)
let leftContent = dataSource?.pageContentView(readerView: self, pageNum: pair.left, containerView: nil)
let leftVC = RDReaderPageChildViewController(contentView: leftContent, pageNum: pair.left)
if let rightPage = pair.right {
let rightContent = dataSource?.pageContentView(readerView: self, pageNum: rightPage, containerView: nil)
let rightVC = RDReaderPageChildViewController(contentView: rightContent, pageNum: rightPage)
pageViewController.setViewControllers([leftVC, rightVC], direction: animated ? direction : .forward, animated: animated, completion: nil)
} else {
// 封面页独占或奇数最后一页:右侧放空白页
let blankNum = isFullScreenPage(pair.left) ? RDReaderView.blankPageNum : RDReaderView.blankEndPageNum
let emptyVC = RDReaderPageChildViewController(contentView: UIView(), pageNum: blankNum)
pageViewController.setViewControllers([leftVC, emptyVC], direction: animated ? direction : .forward, animated: animated, completion: nil)
}
currentPage = pair.left
} else {
let contentView = dataSource?.pageContentView(readerView: self, pageNum: pageNum, containerView: nil)
let vc = RDReaderPageChildViewController(contentView: contentView, pageNum: pageNum)
pageViewController.setViewControllers([vc], direction: animated ? direction : .forward, animated: animated, completion: nil)
currentPage = pageNum
}
default:
collectionView.reloadData()
collectionView.layoutIfNeeded()
collectionView.setContentOffset(layout.currentContentOffset(count: pageNum), animated: animated)
}
}
public func reloadData() {
switchReaderDisplayType(currentDisplayType)
topToolView = self.dataSource?.topToolView?(readerView: self)
bottomToolView = self.dataSource?.bottomToolView?(readerView: self)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension RDReaderView: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
private func makeSinglePageChildVC(for pageNum: Int) -> RDReaderPageChildViewController {
if pageNum == RDReaderView.blankPageNum || pageNum == RDReaderView.blankEndPageNum {
return RDReaderPageChildViewController(contentView: UIView(), pageNum: pageNum)
}
let contentView = dataSource?.pageContentView(readerView: self, pageNum: pageNum, containerView: nil)
return RDReaderPageChildViewController(contentView: contentView, pageNum: pageNum)
}
/// 计算某页的"下一页"页码(考虑封面页插入空白页)
private func nextPageNum(after pageNum: Int, isDualPage: Bool) -> Int? {
let totalPages = dataSource?.pageCountOfReaderView(readerView: self) ?? 0
// 末尾空白页之后没有更多页
if pageNum == RDReaderView.blankEndPageNum {
return nil
}
if isDualPage, let coverIndex = coverPageIndex {
if pageNum == coverIndex {
return RDReaderView.blankPageNum
}
if pageNum == RDReaderView.blankPageNum {
let firstContent = coverIndex + 1
return firstContent < totalPages ? firstContent : nil
}
}
let next = pageNum + 1
if next < totalPages {
return next
}
// pageNum 是最后一页,检查在双页模式下是否需要空白页配对
if isDualPage {
if let coverIndex = coverPageIndex {
// 有封面时:封面之后的页面两两配对,偶数偏移=左页需要配对
let adjustedIndex = pageNum - (coverIndex + 1)
if adjustedIndex >= 0 && adjustedIndex % 2 == 0 {
return RDReaderView.blankEndPageNum
}
} else {
// 无封面:偶数索引=左页需要配对
if pageNum % 2 == 0 {
return RDReaderView.blankEndPageNum
}
}
}
return nil
}
/// 计算某页的"上一页"页码(考虑封面页插入空白页)
private func prevPageNum(before pageNum: Int, isDualPage: Bool) -> Int? {
// 末尾空白页的前一页是最后一个真实页
if pageNum == RDReaderView.blankEndPageNum {
let totalPages = dataSource?.pageCountOfReaderView(readerView: self) ?? 0
return totalPages > 0 ? totalPages - 1 : nil
}
if isDualPage, let coverIndex = coverPageIndex {
if pageNum == RDReaderView.blankPageNum {
return coverIndex
}
if pageNum == coverIndex + 1 {
return RDReaderView.blankPageNum
}
}
let prev = pageNum - 1
return prev >= 0 ? prev : nil
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? RDReaderPageChildViewController else { return nil }
let isDualPage = landscapeDualPageEnabled && isLandscape
let isRTL = pageDirection == .rightToLeft
// RTL 时 before/after 语义互换:before(向右翻)= 下一页
let targetNum = isRTL
? nextPageNum(after: vc.pageNum, isDualPage: isDualPage)
: prevPageNum(before: vc.pageNum, isDualPage: isDualPage)
guard let num = targetNum else { return nil }
let targetVC = makeSinglePageChildVC(for: num)
willPreviousTransitionToViewController = targetVC
return targetVC
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? RDReaderPageChildViewController else { return nil }
let isDualPage = landscapeDualPageEnabled && isLandscape
let isRTL = pageDirection == .rightToLeft
let targetNum = isRTL
? prevPageNum(before: vc.pageNum, isDualPage: isDualPage)
: nextPageNum(after: vc.pageNum, isDualPage: isDualPage)
guard let num = targetNum else { return nil }
let targetVC = makeSinglePageChildVC(for: num)
willNextTransitionToViewController = targetVC
return targetVC
}
public func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed, let firstVC = pageViewController.viewControllers?.first as? RDReaderPageChildViewController {
let pn = firstVC.pageNum
if pn != RDReaderView.blankPageNum && pn != RDReaderView.blankEndPageNum {
currentPage = pn
}
}
}
public func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
willTransitionToViewController = pendingViewControllers.first
}
}
extension RDReaderView: UICollectionViewDataSource, RDReaderFlowLayoutDelegate, RDReaderFlowLayoutDataSoure {
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let identifer = self.dataSource?.pageIdentifier(readerView: self, pageNum: indexPath.row) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifer, for: IndexPath(item: indexPath.row, section: 0)) as! RDReaderContentCell
let conttainerView = self.dataSource?.pageContentView(readerView: self, pageNum: indexPath.row, containerView: cell.containerView)
if let conttainerView = conttainerView {
cell.containerView = conttainerView
}
// RTL 水平模式:翻转 cell 内容使文字方向正常(collectionView 已整体翻转)
if pageDirection == .rightToLeft && currentDisplayType != .verticalScroll {
cell.contentView.transform = CGAffineTransform(scaleX: -1, y: 1)
} else {
cell.contentView.transform = .identity
}
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(UICollectionViewCell.self), for: indexPath)
return cell
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dataSource?.pageCountOfReaderView(readerView: self) ?? 0
}
public func pageNum(flowLayout: RDReaderFlowLayout, pageIndex: Int) {
currentPage = pageIndex
}
public func heigtOfVerticalScrollPage(flowLayout: RDReaderFlowLayout, pageIndex: Int) -> CGFloat? {
return nil
}
}
extension RDReaderView {
public func register(contentView: UIView.Type, contentViewWithReuseIdentifier identifier: String) {
contentViews[identifier] = contentView
collectionView.register(RDReaderContentCell.self, forCellWithReuseIdentifier: identifier)
}
public func dequeueReusableContentView(withReuseIdentifier identifier: String, for pageNum: Int) -> UIView {
if self.currentDisplayType != .pageCurl, let cell = self.collectionView.cellForItem(at: IndexPath(row: pageNum, section: 0)) as? RDReaderContentCell, let containerView = cell.containerView {
return containerView
}
let contentViewClass = contentViews[identifier]
assert(contentViewClass != nil, "请调用register(contentViewcontentViewWithReuseIdentifier:)")
var contentView = contentViewClass!.init()
return contentView
}
public func pageContentView(pageNum: Int) -> UIView? {
if currentDisplayType == .pageCurl {
return (self.pageViewController.viewControllers?.first as? RDReaderPageChildViewController)?.contentView
} else {
let cell = collectionView.cellForItem(at: IndexPath(item: pageNum, section: 0)) as? RDReaderContentCell
return cell?.containerView
}
}
}
private var cellViewKey: Int8 = 0
extension UIView {
var ss_superViewController: UIViewController? {
var next = self.next
while next != nil {
if next is UIViewController {
return next as? UIViewController
} else {
next = next!.next
}
}
return nil
}
}