如何在Swift中更优雅的返回UITableViewCell

2,603 阅读3分钟

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
}

使用方式如下:

  1. 首先实现一个自定义的cell
class CZBCollectionViewCell: UICollectionViewCell { 
  let textLabel = UILabel()
  /// 设置textLabel...
}
  1. 初始化一个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
}
  1. 通过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的情况)

  1. 首先仿照系统定义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))"
    }
}
  1. 实现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点好处

  1. 不需要再进行强制转换
  2. 不需要再进行提前注册或者判断是否为空再进行赋值
  3. 把配置cell的代码从cellForRowAtIndexPath中抽离了出来,和对应的model绑定在了一起