Cell 组件
在 iOS 开发中,当我们需要实现列表需求时,通常会写出 Cell 组件来实现每个列表项的布局和样式。但是这种方式的缺点是,当列表的实现方式从 UITableView 切换到 UICollectionView 时,我们的 Cell 组件就需要做出一些修改,这不够灵活。甚至当我们需要在其他地方使用 Cell 组件时,还需要重新实现一遍,这对于复用和维护来说都不是最佳的解决方案。
当使用 Cell 的方式实现下面的课程组件:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| final class CourseCell: UITableCell {
let coverView = CourseCoverView() let infoView = CourseInfoView() }
final class CourseCoverView: UIView { }
final class CourseInfoView: UIView { }
|
如果现在又来一个新的需求,需要在列表的最上面放一排可以横向滚动的封面图,下面依旧还是课程列表。这里使用 UICollectionView 来实现我们的这个需求,那么需要修改的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| final class CourseCell: UICollectionViewCell {
let coverView = CourseCoverView() let infoView = CourseInfoView() }
final class CourseCoverCell: UICollectionViewCell {
let coverView = CourseCoverView() }
final class CourseCoverView: UIView { }
final class CourseInfoView: UIView { }
|
View 组件
因此,使用 View 组件实现列表项会更加灵活。在这种情况下,Cell 的作用只是一个容器,只是把 View 放到 Cell 上去,并且负责做一些注册和重用的机制。但这种方式的问题是,每当我们把这个 View 用在 UITableView 或 UICollectionView 上时,我们就必须编写对应的 Cell 容器代码,这部分代码的工作基本相同(添加 View,添加约束)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
final class CourseCollectionCell: UICollectionViewCell {
let courseView = CourseView() }
final class CourseTableCell: UITableViewCell {
let courseView = CourseView() }
final class CourseView: UIView {
let coverView = CourseCoverView() let infoView = CourseInfoView() }
final class CourseCoverView: UIView { }
final class CourseInfoView: UIView { }
|
我们可以从上面的示例代码中看出,这种方式在实际的工程中依旧会出现大量的模板代码,所这就需要我们思考有没有什么方式可以来避免编写大量、重复的模板代码了?
CheapCell 是之前的项目重构成 Swift 之后实现的一套能够让你不用写 Cell 容器的库,这个库当时帮助我们减少了上千行的 Cell 容器代码。它的核心代码很简单,最重要的是利用了 Swift 的面相协议编程的方式来实现的,能够极大地减少代码量和提高代码的复用性和可维护性。具体的实现代码可以看我的 Github 仓库 https://github.com/HParis/CheapCell。
下面是集成了 CheapCell 之后的使用方式:
第一步:让我们的 View 组件遵循我们定义的 CheapCell 协议
1 2 3
| extension CourseView: CheapCell {} extension CourseCoverView: CheapCell {}
|
第二步:在 UICollectionView 中的使用方式(UITableView 的使用方式也是类似的)
1 2 3 4 5 6 7
| collectionView.registerCheapCell(CourseView.self) collectionView.registerCheapCell(CourseCoverView.self)
let cell = collectionView.dequeueReusableCheapCell(for: indexPath) as CollectionCell<CourseView> let cell = collectionView.dequeueReusableCheapCell(for: indexPath) as CollectionCell<CourseCoverView>
|
配合第三方库使用
如果你使用了一些第三方的 Cell 的话,比如 SwipeCellKit,那么你可以按照下面的方式来实现你的 CheapCell:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import SwipeCellKit
public extension UICollectionView { func registerCheapSwipeCell<T: CheapCell>(_ : T.Type) { register(CollectionSwipeCell<T>.self, forCellWithReuseIdentifier: CollectionSwipeCell<T>.identifier) }
func dequeueReusableCheapSwaipeCell<T: CheapCell>(for indexPath: IndexPath) -> CollectionSwipeCell<T> { dequeueReusableCell(withReuseIdentifier: CollectionSwipeCell<T>.identifier, for: indexPath) as! CollectionSwipeCell<T> } }
public final class CollectionSwipeCell<T: CheapCell>: SwipeCollectionViewCell { public static var identifier: String { return T.identifier }
public let itemView = T() override init(frame: CGRect) { super.init(frame: frame) setupView() }
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() }
private func setupView() { contentView.addSubview(itemView)
itemView.translatesAutoresizingMaskIntoConstraints = false itemView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true itemView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true itemView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true itemView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true } }
|
现在你就可以直接通过 CheapCell 的方式来使用了:
1 2 3 4 5 6
| collectionView.registerCheapSwipeCell(CourseView.self)
let cell = collectionView.dequeueReusableCheapCell(for: indexPath) as CollectionSwipeCell<CourseView> cell.delegate = self
|