为 UITableView 扩展一个不用注册 Cell 的方法

我们一般创建 UITableViewCell 会这么写:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
    if cell == nil {
        cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
    }
    return cell!
}

如果需要注册,则这么写:

// 注册 cell
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    return cell
}

有区别吗?一个最明显的区别就是,一个是可选的 ( Optional ),一个是一定有值的;还有就是,使用第二种方法时,如果事先未注册 Cell,则会引发崩溃,目的是为了提醒开发者:你忘记注册了!

基于以上两点,官方建议使用第二种方法创建 UITableViewCell

扩展 UITableView

为了解决每次创建 Cell 时都要手动的给它注册一遍的问题,需要给它扩展一个方法,怎么做呢?

我们的目的是不用手法注册,也就意味着第二种方法已经行不通(需要注册),那我们把目光聚焦在第一个方法上。

怎么做呢?解决重复的代码是第一步,假如每次创建 Cell 都要写这么一个判断,那不是烦死了?为了能够重用,我们可以封装一下,比如:

func createCell(tableView: UITableView, style: UITableViewCellStyle, reuseIdentifier: String) -> UITableViewCell {
    var cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier)
    if cell == nil {
        cell = UITableViewCell(style: style, reuseIdentifier: reuseIdentifier)
    }
    return cell!
}

然后我们使用这个自定义创建 Cell 的方法:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = create(tableView: tableView, style: .default, reuseIdentifier: "Cell")
    return cell
}

然则如果把方法封装在 tableView 的 extensions 里面,则更好一些,因为该方法是基于 tableView 来调用的,可以这样:

public extension UITableView {
    func dequeueReusableCell(style: UITableViewCell.CellStyle = .default, identifier: String? = nil) -> UITableViewCell  {
        var cell = dequeueReusableCell(withIdentifier: identifier)
        if cell == nil {
            cell = UITableViewCell(style: style, reuseIdentifier: identifier)
        }
        return cell!
    }
}

来试一下:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(style: .default, identifier: "Cell")
    return cell
}

这样一来感觉就好很多了,看起来和调用系统的方法差不多。但是还有两个问题:

  1. 创建不同类型的 Cell
    在这里我们使用的是 UITableViewCell 类型创建 Cell,如果需要创建不同的类型怎么办?
  2. 创建来自 Nib 的 Cell
    在这里我们只使用代码的方式创建 Cell,如果要创建来自 Nib 的 Cell 怎么办?

我们来逐一解决这两个问题。

创建不同类型的 Cell

解决第一个问题的一个有效的方法就是使用泛型,我们在方法里面新增一个表示 UITableViewCell 的泛型类。如下所示:

func dequeueReusableCell(cellType: T.Type = T.self, style: UITableViewCell.CellStyle = .default, identifier: String? = nil) -> T  {
    var cell = dequeueReusableCell(withIdentifier: identifier ?? "\(cellType)") as? T
    if cell == nil {
        cell = T(style: style, reuseIdentifier: identifier ?? "\ cellType)")
    }
    return cell!
}

来分析一下这个方法,首先方法添加了一个泛型 ,目的是为了能够创建不同类型的 Cell 并返回(注意,返回值也是 T )。

然后接下来的一行代码是:

// 使用重用标识符从缓存池中获取 Cell
var cell = dequeueReusableCell(withIdentifier: identifier ?? "\(cellType)") as? T

该方法的意思注释已经说了:是使用重用标识符从缓存池中获取 Cell。那么这个重用标识符应该传什么呢?你当然可以传方法参数里面的 identifier ,但一个更好的方式是使用 Cell 的类名来作为它的重用标识符。综上所述,在没有指定 identifier 参数的情况下使用 Cell 的类名来作为它重用标识符,它应该传:identifier ?? "\(cellType)"

接下来的一段代码是:

if cell == nil {
    cell = T(style: style, reuseIdentifier: identifier ?? "\(cellType)")
}

在判断 cell 为空时执行,和普通的创建方式一样,只是这里使用了 T 代替 UITableViewCell ,泛型 T 指定了它应该是 UITableViewCell 类,所以本质上它跟 UITableViewCell 类是一样的。

这样一来,在不使用 Nib 的情况下,这个方法完全没有问题了。来试一下:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // 不指定类型,默认返回 `UITableViewCell`。
    // let cell = tableView.dequeueReusableCell()

    // 返回 MyTableViewCell
    // let cell = tableView.dequeueReusableCell() as MyTableViewCell

    // 返回 MyTableViewCell
    // let cell: MyTableViewCell = tableView.dequeueReusableCell()

    // 返回 MyTableViewCell
    let cell = tableView.dequeueReusableCell(cellType: MyTableViewCell.self)

    return cell
}

创建来自 Nib 的 Cell

为了能够区别于代码创建的方式,我们在定义一个专门用来处理创建 Nib Cell 的方法,因为如果都放在一起,逻辑处理起来会比较复杂。

func dequeueReusableNibCell(cellType: T.Type = T.self, nibName: String? = nil) -> T {
    var cell = dequeueReusableCell(withIdentifier: nibName ?? "\(cellType)") as? T
    if cell == nil {
        cell = UINib(nibName: nibName ?? "\(cellType)", bundle: nil).instantiate(withOwner: nil).last as? T
    }
    return cell!
}

其实情况差不多,标识符参数由 identifier 变成了 nibNameUITableViewCell 的创建方式变成了使用 UINib 来创建,最后返回这个 cell

于是你可以像代码创建的方式一样愉快的创建 Nib 中的 UITableViewCell 了。

重要:

使用 Nib 创建方式,xib 里面的 Cell 标识符务必要和方法里面的重用标识符一致,否则很有可能会引发崩溃或未知错误。

你可能感兴趣的:(为 UITableView 扩展一个不用注册 Cell 的方法)