将多个自定义 UITableViewCell 链接到单个 xib

Posted

技术标签:

【中文标题】将多个自定义 UITableViewCell 链接到单个 xib【英文标题】:Linking multiple custom UITableViewCell's to single xib 【发布时间】:2019-01-22 02:22:07 【问题描述】:

问题

我正在创建一个可重用的自定义 UITableViewCell,然后将其子类化以供重用。如何将这些不同的子类设置为链接到同一个 xib 文件?

背景

让我们调用我的自定义 UITableViewCell PickerTableViewCell。这个单元格包括一个UIPickerView,以及关于选择器视图的外观和行为的所有实现。当我想使用这个单元格时,我唯一需要给它的是选择器视图的数据。所以我继承PickerTableViewCell,然后简单地创建我需要的数据源并将其分配给选择器视图。到目前为止,这一切都运作良好。

这里是PickerTableViewCell的相关部分:

class PickerTableViewCell: UITableViewCell 

    var picker: UIPickerView! = UIPickerView()
    var pickerDataSource: PickerViewDataSource!

    override func awakeFromNib() 
        super.awakeFromNib()

        self.picker = UIPickerView(frame: CGRect(x: 0, y: 40, width: 0, height: 0))
        self.picker.delegate = self

        self.assignPickerDataSource()
    

    // Must be overriden by child classes

    func assignPickerDataSource() 
        fatalError("Must Override")
    

这是一个子类的例子:

class LocationPickerTableViewCell: PickerTableViewCell 

    override func assignPickerDataSource() 
        self.pickerDataSource = LocationPickerDataSource()
        self.picker.dataSource = self.pickerDataSource
    

问题

由于我在各处使用这些单元格,并使用不同的数据源,我创建了一个 xib 文件,该文件定义了单元格的外观,称为 PickerTableViewCell.xib,并将其分配给类 PickerTableViewCell。在我想使用它的视图控制器中,我将单元格注册到viewDidLoad() 中的表格视图。然后,在func tableView(_:, cellForRowAt) 中,我将我想要的子类出列,如下所示:

let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! LocationPickerTableViewCell
return cell

这就是问题发生的地方。创建的单元格是PickerTableViewCell,而不是它的子类LocationPickerTableViewCell。这遇到了我放置在被子类覆盖的父类中的致命错误。

我发现解决此问题的唯一方法是为我要创建的每个子类创建一个单独的 xib 文件,并将其分配给相关的子类。虽然这个解决方案确实有效,但在我的项目中拥有所有这些实际上相同的 xib 文件(除了它们被分配到哪个类)感觉不对。

有没有办法可以克服这个问题,让所有这些单元格链接到同一个 xib 文件?

谢谢! :)

【问题讨论】:

别觉得有什么办法,要么按代码布局,要么复制Xib,要么使用扩展 为什么要麻烦子类?如果唯一的区别是数据源,请在cellForRowAt中分配数据源 @Paulw11 那是我没有考虑过的东西。它适用于某些子类,例如上面的 LocationPickerTableViewCell,但是其他一些具有额外的功能,子类可以更好地工作 @RyanSaffer 在 cell 类中通过 `Bundle.main.loadNibNamed' 方法加载 Xib 视图,通过这个你可以在多个类中添加相同的 Xib 视图。 @Rocky 这似乎是一个不错的选择,但我已经尝试过并且遇到了各种问题,例如 '[ setValue:forUndefinedKey:]: 这个类不是键值编码-符合关键 headerLabel。 ...你有机会举个例子吗? 【参考方案1】:

将 xib 加载的视图添加到您要在其中使用它的 UITableViewCell 类。

    根据您的要求设计创建您的 xib,在您的示例中为 PickerTableViewCell.xib

    创建要在其中使用该视图的UITableViewCell 子类。我为此使用FirstTableViewCell & SecondTableViewCell

在表格单元格的构造函数中加载xib并将其添加到表格单元格中。

let nib = Bundle.main.loadNibNamed("PickerTableViewCell", owner: nil, options: nil)
        if let view = nib?.first as? UIView
            self.addSubview(view)
         

如果 xib 有任何 @IBOutlet 则通过 viewWithTag 函数获取它们并分配给类变量

if let label = self.viewWithTag(1) as? UILabel
            self.label = label
        

overridereuseIdentifier每个tableviewCell子类不同名字的var

override var reuseIdentifier: String?
    return "FirstTableViewCell"

现在您可以在需要的地方使用这些类,使用以下步骤:

用 xib 用 tableview 注册这个 tableviewCell 子类:

tableView.register(FirstTableViewCell.self, forCellReuseIdentifier:"PickerTableViewCell")

现在在cellForRowAt indexPath 方法中使用它。

var cell = tableView.dequeueReusableCell(withIdentifier: "FirstTableViewCell", for: indexPath) as? FirstTableViewCell
if cell == nil 
    cell = FirstTableViewCell()

cell?.label?.text = "FirstTableViewCell"

【讨论】:

这个解决方案效果很好!我有一个 xib,并且能够根据任何类的需要使用它,甚至是子类。谢谢!!!【参考方案2】:

不要使用子类化来分配不同的数据源。

方法一:在tableView(_:cellForRowAt:)中赋值pickerDataSource

在表格视图控制器中,需要分配pickerDataSource

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! PickerTableViewCell
    cell.pickerDataSource = LocationPickerDataSource()

    return cell

使用didSet 处理分配pickerDataSource 后所需的额外工作。

class PickerTableViewCell: UITableViewCell 

    var picker: UIPickerView! = UIPickerView()
    var pickerDataSource: PickerViewDataSource! 
        didSet 
            self.picker.dataSource = self.pickerDataSource
        
    

    …

方法 2:以所有需要的方式扩展 PickerTableViewCell

这里不是子类化,而是将所需的逻辑添加到一个唯一命名的设置方法中,每个设置方法都定义在自己的扩展中。

extension PickerTableViewCell 

    func setupLocationPickerDataSource() 
        self.pickerDataSource = LocationPickerDataSource()
        self.picker.dataSource = self.pickerDataSource
    


然后在tableView(_:cellForRowAt:)

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! PickerTableViewCell
    cell.setupLocationPickerDataSource()

    return cell

【讨论】:

感谢您提供如此详细的答案!虽然这些方法非常接近,但我没有将其标记为正确的原因有两个: 1. 我的子类具有覆盖某些方法的附加功能,这是使用扩展无法实现的。 2. 给定问题 1,出队的单元格仍然是 PickerTableViewCell,而不是 LocationPickerTableViewCell。请参阅标记为正确的答案,该答案能够链接到单个 xib,同时保留我的子类设计。谢谢!!

以上是关于将多个自定义 UITableViewCell 链接到单个 xib的主要内容,如果未能解决你的问题,请参考以下文章

如何正确链接自定义 UITableViewCell 和访问元素

将项目添加到 .plist 并在自定义 UITableViewCell 中显示

多个自定义 UITableViewCell 相互覆盖 Swift

如何在 swift 中使自定义 UITableViewCell 中的链接可点击

在 Swift 中将自定义 UITableViewCell 共享到多个 UITableViewController

可折叠部分和每个部分的多个自定义 UITableViewCell