|
@@ -0,0 +1,186 @@
|
|
|
+//
|
|
|
+// TSReusableCollectionView.swift
|
|
|
+// Pods
|
|
|
+//
|
|
|
+// Created by 100Years on 2025/4/25.
|
|
|
+//
|
|
|
+import UIKit
|
|
|
+
|
|
|
+// 单元格配置协议
|
|
|
+public protocol TSSimpleConfigurableView {
|
|
|
+ var data:Any? { get set }
|
|
|
+ var delegate:TSSimpleCollectionViewDelegate? { get set }
|
|
|
+ var indexPath:IndexPath? { get set }
|
|
|
+}
|
|
|
+
|
|
|
+// 单元格事件类型
|
|
|
+public enum TSSimpleCellAction {
|
|
|
+ case tap
|
|
|
+ case buttonTapped(String) // 支持自定义按钮标识
|
|
|
+ case custom(String)
|
|
|
+}
|
|
|
+
|
|
|
+// 回调参数结构体
|
|
|
+public struct TSSimpleCellEvent {
|
|
|
+ public let action: TSSimpleCellAction
|
|
|
+ public let indexPath: IndexPath
|
|
|
+ public let data: Any?
|
|
|
+
|
|
|
+ public init(action: TSSimpleCellAction, indexPath: IndexPath, data: Any) {
|
|
|
+ self.action = action
|
|
|
+ self.indexPath = indexPath
|
|
|
+ self.data = data
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 集合视图回调协议
|
|
|
+public protocol TSSimpleCollectionViewDelegate: AnyObject {
|
|
|
+ func collectionView(didTrigger event: TSSimpleCellEvent)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+public class TSSimpleSectionData {
|
|
|
+ public var title: String = ""
|
|
|
+ public var items: [Any] = []
|
|
|
+
|
|
|
+ public init(
|
|
|
+ title:String = "",
|
|
|
+ items:[Any] = []
|
|
|
+ ) {
|
|
|
+ self.title = title
|
|
|
+ self.items = items
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+open class TSSimpleCollectionView: UIView {
|
|
|
+
|
|
|
+ public lazy var layout: UICollectionViewFlowLayout = {
|
|
|
+ let layout = UICollectionViewFlowLayout()
|
|
|
+ layout.scrollDirection = .vertical
|
|
|
+ return layout
|
|
|
+ }()
|
|
|
+
|
|
|
+ // MARK: - Properties
|
|
|
+ public lazy var collectionView: UICollectionView = {
|
|
|
+ let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
|
|
+ // 禁用自动 contentInset 调整
|
|
|
+ if #available(iOS 11.0, *) {
|
|
|
+ cv.contentInsetAdjustmentBehavior = .never
|
|
|
+ }
|
|
|
+ cv.backgroundColor = .clear
|
|
|
+ cv.delegate = self
|
|
|
+ cv.dataSource = self
|
|
|
+ return cv
|
|
|
+ }()
|
|
|
+
|
|
|
+ public var sections: [TSSimpleSectionData] = []
|
|
|
+ public weak var delegate: TSSimpleCollectionViewDelegate?
|
|
|
+
|
|
|
+ public var cellTypes: [String: (UIView & TSSimpleConfigurableView).Type] = [:]
|
|
|
+ public var cellIdentifierForItem: ((Any) -> String)?
|
|
|
+ // MARK: - Initialization
|
|
|
+ override init(frame: CGRect) {
|
|
|
+ super.init(frame: frame)
|
|
|
+ setupViews()
|
|
|
+ }
|
|
|
+
|
|
|
+ required public init?(coder: NSCoder) {
|
|
|
+ super.init(coder: coder)
|
|
|
+ setupViews()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func setupViews() {
|
|
|
+ addSubview(collectionView)
|
|
|
+ collectionView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ collectionView.topAnchor.constraint(equalTo: topAnchor),
|
|
|
+ collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
|
+ collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
|
+ collectionView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
|
+ ])
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - Public Methods
|
|
|
+ public func registerCell<Cell: UIView & TSSimpleConfigurableView>(_ cellType: Cell.Type, identifier: String) {
|
|
|
+ cellTypes[identifier] = cellType
|
|
|
+ collectionView.register(cellType, forCellWithReuseIdentifier: identifier)
|
|
|
+ }
|
|
|
+
|
|
|
+ public func registerSectionHeader<Cell: UIView & TSSimpleConfigurableView>(_ cellType: Cell.Type, identifier: String) {
|
|
|
+ cellTypes[identifier] = cellType
|
|
|
+ collectionView.register(cellType, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: identifier)
|
|
|
+ }
|
|
|
+
|
|
|
+ public func registerSectionFooter<Cell: UIView & TSSimpleConfigurableView>(_ cellType: Cell.Type, identifier: String) {
|
|
|
+ cellTypes[identifier] = cellType
|
|
|
+ collectionView.register(cellType, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: identifier)
|
|
|
+ }
|
|
|
+
|
|
|
+ public func reload(with sections: [TSSimpleSectionData]) {
|
|
|
+ self.sections = sections
|
|
|
+ collectionView.reloadData()
|
|
|
+ }
|
|
|
+
|
|
|
+ public func reloadItem(at indexPath: IndexPath) {
|
|
|
+ collectionView.reloadItems(at: [indexPath])
|
|
|
+ }
|
|
|
+
|
|
|
+ public func reloadSection(_ section: Int) {
|
|
|
+ collectionView.reloadSections(IndexSet(integer: section))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - UICollectionViewDataSource & Delegate
|
|
|
+extension TSSimpleCollectionView: UICollectionViewDataSource, UICollectionViewDelegate {
|
|
|
+ public func numberOfSections(in collectionView: UICollectionView) -> Int {
|
|
|
+ return sections.count
|
|
|
+ }
|
|
|
+
|
|
|
+ public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
|
+ return sections[section].items.count
|
|
|
+ }
|
|
|
+
|
|
|
+ public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
|
+ let data = sections[indexPath.section].items[indexPath.item]
|
|
|
+ let identifier = cellIdentifierForItem?(data) ?? String(describing: type(of: data))
|
|
|
+
|
|
|
+ guard let cellType = cellTypes[identifier] else {
|
|
|
+ fatalError("未注册对应数据类型的单元格: \(identifier)")
|
|
|
+ }
|
|
|
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
|
|
|
+
|
|
|
+ if var configurableCell = cell as? (UICollectionViewCell & TSSimpleConfigurableView) {
|
|
|
+
|
|
|
+ configurableCell.data = data
|
|
|
+ weak var delegate: TSSimpleCollectionViewDelegate? = delegate
|
|
|
+ configurableCell.delegate = delegate
|
|
|
+ configurableCell.indexPath = indexPath
|
|
|
+ }
|
|
|
+
|
|
|
+ return cell
|
|
|
+ }
|
|
|
+
|
|
|
+ public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
|
+ let data = sections[indexPath.section].items[indexPath.item]
|
|
|
+ let event = TSSimpleCellEvent(action: .tap, indexPath: indexPath, data: data)
|
|
|
+ delegate?.collectionView(didTrigger: event)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
|
|
|
+ let data = sections[indexPath.section]
|
|
|
+ let identifier = cellIdentifierForItem?(data) ?? String(describing: type(of: data))
|
|
|
+
|
|
|
+ guard let cellType = cellTypes[identifier] else {
|
|
|
+ fatalError("未注册对应数据类型的区间头尾: \(identifier)")
|
|
|
+ }
|
|
|
+
|
|
|
+ let reusableView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: identifier, for: indexPath)
|
|
|
+ if var configurableCell = reusableView as? (UICollectionViewCell & TSSimpleConfigurableView) {
|
|
|
+ configurableCell.data = data
|
|
|
+ configurableCell.delegate = delegate
|
|
|
+ configurableCell.indexPath = indexPath
|
|
|
+ }
|
|
|
+ return reusableView
|
|
|
+ }
|
|
|
+}
|