首页 > 解决方案 > 使用协议对类似 UITableViewCell 进行分组以减少代码

问题描述

一些背景

假设我必须显示一个可以包含各种类型组件的表单:

  1. 文本域
  2. 文本区
  3. 图片
  4. 视频
  5. 落下

我已经UITableViewCell为每一个创建了,但是因为它们都是表单组件,并且它们中的每一个都有一些共同的属性,比如一些数据(formData,userEntered data)等等,我制定了一个协议,并使所有这些单元格都符合这个协议

protocol FormComponent where Self: UITableViewCell {
  var property1: String? { get set }
  var property2: String? { get set }
}

并像这样使我的细胞一致

class TextFieldCell: UITableViewCell, FormComponent {
    var property1: String?
    var property2: String?
}

问题

现在,当我必须决定UITableViewCell要创建哪个表单组件时,我必须创建一个 switch 语句并决定要创建哪个表单组件

// Some field that I get from some computation and this same will go inside every cell no matter the type
let field = computeFieldValue() 

switch fieldType {

case textField:

let cell = tableView.dequeueReusableCell(withIdentifier: TEXT_FIELD_CELL, for: indexPath) as! TextFieldCell

cell.property1 = field.property1
cell.property2 = field.property2

return cell

case textArea:

let cell = tableView.dequeueReusableCell(withIdentifier: TEXT_AREA_CELL, for: indexPath) as! TextAreaCell

cell.property1 = field.property1
cell.property2 = field.property2

return cell

}

现在,我不想初始化单元并分配case开关内部的属性,而是想利用协议优势将开关外部的单元初始化为一种协议类型,以便我可以分配开关外部的属性值并且不要' t 需要为每种情况下的属性分配相同的值

// Some field that I get from some computation and this same will go inside every cell no matter the type
let field = computeFieldValue() 

var cell = // cell initialisation which will be of type FormComponent so that I can access the property values directly from here 

cell.property1 = field.property1
cell.property2 = field.property2

switch fieldType {

case textField:

// Converting that cell to TextFieldCell with the properties defined above intact

case textArea:

// Converting that cell to TextAreaCell with the properties defined above intact
}

return cell

我认为这有点像 Upcasting,我不太确定如何实现这一点。如果有一些特定于单个 fieldType 的属性,我可以向下转换为喜欢cell as! TextFieldCellcase部分并分配它

而且我有很多属性和很多案例(fieldType)要处理,所以这种方法会大大减少代码

标签: iosswifttableviewswift-protocols

解决方案


很少有模型和类的创作。你已经实现了这些,但为了检查我添加了它们

  1. FieldType枚举

    enum FieldType {
        case textArea, textLabel, textField
    }
    
  2. Field结构模型。您可能正在使用类,并且可能有更多属性

    struct Field {
        var type : FieldType
        var property1 : String
        var property2 : String
    }
    
  3. 现在您的协议,正如您在问题中已经提到的那样

    protocol FormComponent where Self: UITableViewCell {
    
        var property1: String? { get set }
        var property2: String? { get set }
    }
    
  4. 还有一些不同种类的细胞,问题中提到了其中一个。

    class TextFieldCell: UITableViewCell, FormComponent {
    
        var property1: String?
        var property2: String?
    }
    
    class TextAreaCell: UITableViewCell, FormComponent {
    
        var property1: String?
        var property2: String?
    }
    
    class TextLabelCell: UITableViewCell, FormComponent {
    
        var property1: String?
        var property2: String?
    }
    

现在要使所有这些工作正常进行,您需要使用我在评论中提到的协议扩展。

extension FormComponent {
     func setProperties(p1 : String, p2 : String)  {
        self.property1 = p1
        self.property2 = p2
    }
}

现在cellForRow,要制作可重用的代码,您需要编写如下代码

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let field = arrFields[indexPath.row]
    var cell : FormComponent?

    switch field.type {
    case .textArea:
         cell = tableView.dequeueReusableCell(withIdentifier: "TextAreaCell") as! TextAreaCell
    case .textLabel:
         cell = tableView.dequeueReusableCell(withIdentifier: "TextLabelCell") as! TextLabelCell
    case .textField:
         cell = tableView.dequeueReusableCell(withIdentifier: "TextViewCell") as! TextFieldCell
    }

    cell!.setProperties(p1: field.property1, p2: field.property2)

    return cell!
}

编辑:当您将类型转换FormComponentTextFieldCell. 如果万一它不起作用,请尝试下面的开关盒内的代码

case .textField:
    cell = tableView.dequeueReusableCell(withIdentifier: "TextFieldCell") as! TextFieldCell
    let newObject = cell as! TextFieldCell // type cast your cell
    newObject.yourProperty = "New Property" // access your specified property 

而已。


注意:cellForRow中,单元格对象是可选的,所以请确保它不会通过你的switch-caseselse 你的应用程序将崩溃。


推荐阅读