UITableView可以说是最常用的控件之一,平常写需求时,最常写的方法之一就是下面的代码(注:由于我们项目是不使用xib的,不考虑xib的情况)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//未提前注册
var cell = tableView.dequeueReusableCell(withIdentifier: "identifier") as? CZBCell
if cell == nil {
cell = CZBCell(style: .default, reuseIdentifier: "identifier")
}
cell.config(model:model)
return cell!
//提前注册,可能会向下面那么写
var cell = tableView.dequeueReusableCell(withIdentifier: "identifier") as! CZBCell
cell.config(model:model)
return cell
}
由于项目中最近接入了SwiftLint,其中有条规则是不能使用强制解包,否则就会有警告,于是就想有没有更好的方式来返回cell
在介绍方案之前,先看一下,在iOS14中,有个新的结构体叫CellRegistration,是UICollectViewCell的内部结构体,并且可以通过CellRegistration来获取cell,定义以及API如下:
extension UICollectionView {
public struct CellRegistration<Cell, Item> where Cell : UICollectionViewCell {
/// 配置cell的时候的回调
public typealias Handler = (Cell, IndexPath, Item) -> Void
///此方法是获取代码实现的cell
public init(handler: @escaping UICollectionView.CellRegistration<Cell, Item>.Handler)
///此方法是用于获取XIb实现的cell
public init(cellNib: UINib, handler: @escaping UICollectionView.CellRegistration<Cell, Item>.Handler)
}
/// 通过配置以及indexPath和所需要的mode来返回一个配置好的cell
public func dequeueConfiguredReusableCell<Cell, Item>(using registration: UICollectionView.CellRegistration<Cell, Item>, for indexPath: IndexPath, item: Item?) -> Cell where Cell : UICollectionViewCell
}
使用方式如下:
- 首先实现一个自定义的cell
class CZBCollectionViewCell: UICollectionViewCell {
let textLabel = UILabel()
/// 设置textLabel...
}
- 初始化一个CellRegistration
// 这里调用了CellRegistration的初始化方法,后面是个尾随闭包,
// 前面加UICollectionView是因为CellRegistration声明在UICollectionView的扩展内
// 这不是UICollectionView的方法,这不是UICollectionView的方法
// 这个handler会在collectionView调用dequeueConfiguredReusableCell的时候调用
// 然后把这个config存起来,比如作为VC的属性
let config = UICollectionView.CellRegistration<MyCollectionViewCell, String> { (cell, indexPath, text) in
cell.textLabel.text = text
}
- 通过UICollectView以及CellRegistration来返回一个cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// 在这里使用config,来返回一个已经配置好的cell,这里不需要提前注册就能返回一个固定类型的cell,不需要转换
// cell indexPath item都会传到config的handle里进行调用
return collectionView.dequeueConfiguredReusableCell(using: config,for: indexPath,item: "text---text")
}
这样就能够解决我上面强转cell遇到的警告的问题,但UITabelView没有上述的API,既然没有,那就仿照系统来实现一下,(未考虑XIB的情况)
- 首先仿照系统定义CellRegistration
public struct CZBCellRegistration<Cell, Item> where Cell : UITableViewCell {
public typealias Handler = (Cell, IndexPath, Item) -> Void
/// 配置cell时需要调用的回调
public let handler: Handler
/// 通过Item和Cell的类型生成唯一标识符号
fileprivate let identifier: String
public init(handler: @escaping Handler){
self.handler = handler
self.identifier = "\(type(of: Item.self))_\(type(of: Cell.self))"
}
}
- 实现dequeueConfiguredReusableCell方法
共分为2步,首先,通过CellRegistration生成cell,然后调用CellRegistration的handler,代码如下:
extension UITableView {
func dequeueConfiguredReusableCell<Cell, Item>(using registration: CZBCellRegistration<Cell, Item>, for indexPath: IndexPath, item: Item) -> Cell where Cell : UITableViewCell {
let cell = getCell(with: registration)
// 调用CellRegistration的handler
registration.handler(cell, indexPath, item)
return cell
}
/// 通过CellRegistration生成cell
private func getCell<Cell, Item>(with registration:CZBCellRegistration<Cell, Item>) -> Cell where Cell : UITableViewCell {
if let cell = dequeueReusableCell(withIdentifier: registration.identifier) as? Cell {
return cell
}else {
let cell = Cell(style: .default, reuseIdentifier: registration.identifier)
return cell
}
}
}
使用和上面的UICollectView基本一样,示例如下:
let cellConfig = CZBCellRegistration<CZBCell, CZBModel> { (cell, indexpath, model) in
cell.updateUI(model: model)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueConfiguredReusableCell(using: cellConfig, for: indexPath, item: dataSourceArray[indexPath.row])
}
这样改完之后,有3点好处
- 不需要再进行强制转换
- 不需要再进行提前注册或者判断是否为空再进行赋值
- 把配置cell的代码从cellForRowAtIndexPath中抽离了出来,和对应的model绑定在了一起