关于ios:注册和出列特定类型的UITableViewCell

Register and dequeue UITableViewCell of specific type

UITableView 是在 objc 已经"旧"并且从未提及 swift 时构建的。在 objc 中,如果您将一个单元格出列来投射它,则无需将其分配,您只需分配它,只要您拥有正确的单元格,一切都会顺利进行。

随着 swift 泛型的力量来到 iOS(和其他苹果)平台。

现在我发现自己一次又一次地写了很多样板文件(定义一个标识符,转换单元格,使用强制解包致命错误,..)。

所以我想知道是否有一些想法可以使代码更易于使用和更清晰。


@Saren 您的解决方案适用于单个 UITableViewCell,但我对其进行了一些增强,支持多个 UITableViewCell 注册。

UITableView 扩展.swift

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
40
41
42
43
public protocol ClassNameProtocol {
    static var className: String { get }
    var className: String { get }
}

public extension ClassNameProtocol {
    public static var className: String {
        return String(describing: self)
    }

    public var className: String {
        return type(of: self).className
    }
}

extension NSObject: ClassNameProtocol {}


public extension UITableView {
    public func register<T: UITableViewCell>(cellType: T.Type) {
        let className = cellType.className
        let nib = UINib(nibName: className, bundle: nil)
        register(nib, forCellReuseIdentifier: className)
    }

    public func register<T: UITableViewCell>(cellTypes: [T.Type]) {
        cellTypes.forEach { register(cellType: $0) }
    }

    public func dequeueReusableCell<T: UITableViewCell>(with type: T.Type, for indexPath: IndexPath) -> T {
        return self.dequeueReusableCell(withIdentifier: type.className, for: indexPath) as! T
    }

    public func registerHeaderFooter<T: UITableViewHeaderFooterView>(HeaderFooterType: T.Type) {
        let className = HeaderFooterType.className
        let nib = UINib(nibName: className, bundle: nil)
        register(nib, forHeaderFooterViewReuseIdentifier: className)
    }

    public func registerHeaderFooter<T: UITableViewHeaderFooterView>(HeaderFooterTypes: [T.Type]) {
        HeaderFooterTypes.forEach { registerHeaderFooter(HeaderFooterType: $0) }
    }
}

使用喜欢

1
2
fileprivate let arrCells = [ContactTableViewCell.self,ContactDetailsCell.self]
self.register(cellTypes: arrCells)

注意:请确保您的类名和 UITableViewCell 的 reusableidenfer 应该相同。


解决这个问题的一个简单方法是编写一个小扩展。

如果未注册的单元出列,此解决方案将导致致命错误。这已经是 iOS 的默认行为,如果我们调用 dequeueReusableCell(withIdentifier:for:) 如果我们还没有注册单元格。

为了让它工作,我们需要一种方法来为我们将使用泛型注册和出列的任何类型的单元格创建一个唯一标识符。如果您想有一种方法为不同的标识符出列同一个单元格,那么您将不得不回退到默认系统(从来没有任何需要)。

所以让我们创建一个名为 UITableView+Tools.swift 的类(或者你喜欢的任何名称)。

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
extension UITableView {
    private func reuseIndentifier< T >(for type: T.Type) -> String {
        return String(describing: type)
    }

    public func register<T: UITableViewCell>(cell: T.Type) {
        register(T.self, forCellReuseIdentifier: reuseIndentifier(for: cell))
    }

    public func register<T: UITableViewHeaderFooterView>(headerFooterView: T.Type) {
        register(T.self, forHeaderFooterViewReuseIdentifier: reuseIndentifier(for: headerFooterView))
    }

    public func dequeueReusableCell<T: UITableViewCell>(for type: T.Type, for indexPath: IndexPath) -> T {
        guard let cell = dequeueReusableCell(withIdentifier: reuseIndentifier(for: type), for: indexPath) as? T else {
            fatalError("Failed to dequeue cell.")
        }

        return cell
    }

    public func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView>(for type: T.Type) -> T {
        guard let view = dequeueReusableHeaderFooterView(withIdentifier: reuseIndentifier(for: type)) as? T else {
            fatalError("Failed to dequeue footer view.")
        }

        return view
    }
}

所以现在我们在我们的类(即视图控制器)中所要做的就是注册单元格(不需要标识符)并将其出列(没有标识符,不强制转换,强制解包或使用保护手动解包)

1
2
3
4
5
6
7
8
9
10
11
12
func viewDidLoad {
    ...

    tableView.register(MyCustomCell.self)
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = dequeueReusableCell(forType: MyCustomCell.self, for: indexPath)
    cell.viewModel = cellModel(for: indexPath)

    return cell
}

就是这样。希望你喜欢这个主意。欢迎任何其他(更好或更坏??)的想法。