在表格视图中放置不同的自定义单元格类型

Posted

技术标签:

【中文标题】在表格视图中放置不同的自定义单元格类型【英文标题】:Putting different custom cell types in a tableview 【发布时间】:2018-05-20 15:41:24 【问题描述】:

我在尝试使用不同的自定义单元格类型填充单个 tableview 时遇到问题。我试图删除代码中所有不相关的部分。我可能有很多后续问题,但在 dequereusablecell 语句(在 cellforrow 函数中)中,我无法将单元格向下转换为存储在数组 cellTypes 中的类型 FilterTableViewCell 或 FilterTableViewCell2 .我收到错误“数组类型现在用元素类型周围的括号写入”......它认为我试图向下转换为数组类型,而不是数组 cellTypes 的给定索引中的单元格类型。我希望这很清楚。

我还尝试在 switch 语句的每种情况下都放置一个 dequereusablecell 语句,但在我的代码的其他部分出现错误。它基本上将 FIlterTableViewCell 和 FilterTableViewCell2 视为基类类型(UITableViewCell),因此它无法访问这两个子类中的属性。我希望这很清楚。但如果您发现我的代码的其他部分有任何问题(尤其是我如何使用数组),请告诉我。

代码:

import Foundation
import UIKit

class FilterViewController: UIViewController, UITableViewDelegate,UITableViewDataSource, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate,UITextViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate
    let properties = ["Diet","Body Type","Looking For","Ethnicity"]
    var values:NSArray!
    let dietOpts:NSArray = ["Vegan","Plant-based","Vegetarian","Meat Eater"]
    let bodytypeOpts:NSArray = ["Muscular","Athletic","Lean","Husky","Chubby","Large","Fat"]
    let lookingforOpts:NSArray = ["Friends","Dates","Chat","Networking","Relationship","Right Now"]
    let ethnicityOpts:NSArray = ["Hispanic/Latino","Black","White","Middle Eastern","South Asian","Asian","Native American","Pacific Islander"]

    var userFilterTable : UITableView!

    var cellTypes = [FilterTableViewCell.self,FilterTableViewCell.self,FilterTableViewCell2.self,FilterTableViewCell2.self]
    var reminderCells = [FilterTableViewCell(),FilterTableViewCell(),FilterTableViewCell2(),FilterTableViewCell2()]
    let cellIDs = ["cellId","cellId","cellId2","cellId2"]
    var toolBar : UIToolbar!
    var i:Int!
    var myUIPicker = UIPickerView()
    var multiplePicker = MultiplePicker()
    var myValues: NSArray!

    override func viewDidLoad() 
        super.viewDidLoad()

        view.backgroundColor = UIColor.blue

        let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
        let displayWidth: CGFloat = self.view.frame.width
        let displayHeight: CGFloat = self.view.frame.height
        userFilterTable = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))

        userFilterTable.register(FilterTableViewCell.self, forCellReuseIdentifier: "cellId")
        userFilterTable.register(FilterTableViewCell2.self, forCellReuseIdentifier: "cellId2")

        userFilterTable.backgroundColor = UIColor.clear
        userFilterTable.isScrollEnabled = false
        userFilterTable.delegate = self
        userFilterTable.dataSource = self
        userFilterTable.rowHeight = 40

        self.userFilterTable.reloadData()
        self.view.addSubview(userFilterTable)
    

    @objc func buttonAction(_ sender: UIButton!)
        print("Button tapped")
    

    func generalPicker()
        myUIPicker.becomeFirstResponder()
        self.myUIPicker.delegate = self
        self.myUIPicker.dataSource = self
    

    @objc func donePicker() 
        reminderCells[i].valueTextField.resignFirstResponder()
    

    func doMultiplePicker()
        multiplePicker.becomeFirstResponder()
        //multiplePicker.delegate = self
        //multiplePicker.dataSource = self

        reminderCells[i].valueTextField.backgroundColor = UIColor.white
        toolBar = UIToolbar()
        toolBar.sizeToFit()
    

    @objc func donePicker2() 
        reminderCells[i].valueTextField.text = multiplePicker.str
        reminderCells[i].valueTextField.resignFirstResponder()
    

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        reminderCells[indexPath.row].valueTextField.becomeFirstResponder()

        i = indexPath.row
        print(properties[indexPath.item]) //print("Diet")
        switch properties[indexPath.item] 
        case "Diet":
            myValues = dietOpts
            generalPicker()
        case "BodyType":
            myValues = bodytypeOpts
            generalPicker()
        case "Looking For":
            myValues = lookingforOpts
            generalPicker()
        case "Ethnicity":
            myValues = bodytypeOpts
            doMultiplePicker()
        default:
            print("Some other character")
        
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        // retuen no of rows in sections
        return properties.count
    

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

        print(indexPath.row)
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIDs[indexPath.row], for: indexPath) as! cellTypes[indexPath.row]

        switch properties[indexPath.item]
            case "Diet","Body Type":
                let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(donePicker))
                toolBar.setItems([doneButton], animated: false)
                cell.valueTextField.inputAccessoryView = toolBar
                cell.valueTextField.inputView = myUIPicker
            case "Looking For","Ethnicity":
                multiplePicker.frame = CGRect(x: 0, y: 0, width: 100, height: 300)
                multiplePicker.myValues = ethnicityOpts as! [String]
                let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(donePicker2))
                toolBar.setItems([doneButton], animated: false)
                cell.valueTextField.inputAccessoryView = toolBar
                cell.valueTextField.inputView = multiplePicker
            default:
                print("default")

        
        cell.backgroundColor = UIColor.clear
        cell.property.text = properties[indexPath.item]
        cell.isUserInteractionEnabled = true
        cell.valueTextField.isUserInteractionEnabled = true
        cell.valueTextField.delegate = self
        cell.valueTextField.tag = indexPath.item
        toolBar = UIToolbar()
        toolBar.sizeToFit()
        reminderCells[indexPath.item] = cell
        return cell
    

    // data method to return the number of column shown in the picker.
    func numberOfComponents(in pickerView: UIPickerView) -> Int 
        return 1
    

    // data method to return the number of row shown in the picker.
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int 
        print(myValues.count)
        return myValues.count
    

    // delegate method to return the value shown in the picker
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? 
        return myValues[row] as? String
    

    // delegate method called when the row was selected.
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) 
        reminderCells[i].valueTextField.text = myValues[row] as? String
    

在 Matt 响应后对 cellForRowAt 进行编辑(这次我在编译时指定类型):

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

    switch properties[indexPath.item]
        case "Diet","Body Type":
            let cell = tableView.dequeueReusableCell(withIdentifier: cellIDs[indexPath.row], for: indexPath) as! FilterTableViewCell
            let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(donePicker))
            toolBar.setItems([doneButton], animated: false)
            cell.valueTextField.inputAccessoryView = toolBar
            cell.valueTextField.inputView = myUIPicker

            cell.backgroundColor = UIColor.clear
            cell.property.text = properties[indexPath.item]
            cell.isUserInteractionEnabled = true
            cell.valueTextField.isUserInteractionEnabled = true
            cell.valueTextField.delegate = self
            cell.valueTextField.tag = indexPath.item
            toolBar = UIToolbar()
            toolBar.sizeToFit()
            reminderCells[indexPath.item] = cell
            return cell
        case "Looking For","Ethnicity":
            let cell = tableView.dequeueReusableCell(withIdentifier: cellIDs[indexPath.row], for: indexPath) as! FilterTableViewCell2
            multiplePicker.frame = CGRect(x: 0, y: 0, width: 100, height: 300)
            multiplePicker.myValues = ethnicityOpts as! [String]
            let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(donePicker2))
            toolBar.setItems([doneButton], animated: false)
            cell.valueTextField.inputAccessoryView = toolBar
            cell.valueTextField.inputView = multiplePicker

            cell.backgroundColor = UIColor.clear
            cell.property.text = properties[indexPath.item]
            cell.isUserInteractionEnabled = true
            cell.valueTextField.isUserInteractionEnabled = true
            cell.valueTextField.delegate = self
            cell.valueTextField.tag = indexPath.item
            toolBar = UIToolbar()
            toolBar.sizeToFit()
            reminderCells[indexPath.item] = cell
            return cell
        default:
            print("default")

    

【问题讨论】:

是的,我在代码的其他部分遇到了编译错误,它们都涉及对提醒Cells[indexPath.row]的引用......它将它视为基类类型(UITableViewCell)而不是自定义类型(FilterTableViewCell 的)...我什至还不能运行代码。我需要修复 cellforrowat tableview 函数中的“dequereusablecell”语句:let cell = tableView.dequeueReusableCell(withIdentifier: cellIDs[indexPath.row], for: indexPath) as! cellTypes[indexPath.row] ... 具体错误我获取这一行是“数组类型现在用括号括住元素类型” 【参考方案1】:

您的dequeue 致电cellForRowAt 提出的这个想法巧妙且看似自然,但却是非法的:

as! cellTypes[indexPath.row]

Swift 不允许您将 (as!) 转换为直到运行时才指定的类型。它是一种静态语言,而不是动态语言。该类型必须是在编译时指定的 literal 类型。

您只需将 cellForRowAt 实现拆分为两个完全独立的开关案例,一个用于第 0 行和第 1 行,一个用于第 2 和 3 行,并为每个执行不同的 dequeue,每个声明并指定其自己的本地 cell 变量,并使用强制转换 as! FilterTableViewCellas! FilterTableViewCell2 literally — 并从那里开始,单独,在每种情况下。

您无法像优雅的代码尝试那样统一这两种情况,因为只有在 cell 被键入字面意思为 FilterTableViewCell 或 FilterTableViewCell2 的上下文中,您才能访问属于各自的类型。

至于这个表达方式:

var reminderCells = [FilterTableViewCell(),FilterTableViewCell(),
                     FilterTableViewCell2(),FilterTableViewCell2()]

...这里的问题是 Swift 只能推断这一定是一个 UITableViewCell 数组,它没有 FilterTableViewCell 属性。

您可以通过定义第三种类型来解决此问题,例如FilterTableViewCellParent,作为FilterTableViewCell和FilterTableViewCell2的公共超类,并赋予it它们可以继承的公共属性; reminderCells 可以是 FilterTableViewCellParent 的数组,您可以通过其中的元素访问属性。 (当然,您也可以将 FilterTableViewCell 设为 FilterTableViewCell2 的超类,或者反之亦然,因此只需使用两种类型。)

但我个人认为你应该放弃“优雅”的尝试,在切换的两种情况下完全分开做所有事情,即使以一些重复为代价。有时,必须像超人穿裤子一样编程;他可能是超人,但他仍然必须一次进入一条腿。

【讨论】:

嘿,马特。明白了。我编辑了我的帖子,并添加了新版本的 cellForRowAt 函数代码。好的,现在我得到的错误涉及我的代码中引用remindCells [i]或remindCells [indexPath.row]的任何地方......错误:“'UITableViewCell'类型的值没有成员'valueTextField'”......基本上swift 不将reminderCells[ ] 识别为自定义单元格类型(FilterTableViewCell 或FilterTableViewCell2,两者都具有“valueTextField”属性并且都是UITableViewCell 的子类)......它将remindCells[] 作为基类类型...... 同样的问题。它看起来很优雅,但你不能那样做。编译器需要在运行时知道这是一个FilterTableViewCell 或FilterTableViewCell2。您不能通过查找数组来“优雅地”做到这一点。这两种类型唯一的共同点是它们是 UITableViewCells,而 UITableViewCell 没有valueTextField 属性。您可以通过检查reminderCells 的推断类型来看到这一点。您可以通过使确实具有valueTextField的某些第三种单元格类型的FilterTableViewCell和FilterTableViewCell2子类来“解决”它,但您还没有这样做。 hmm 那么当我在same 类型的reminderCells 中制作所有项目时如何...例如,当我制作所有项目FilterTableViewCell() 对象时,我不明白任何错误。为什么只有当reminderCells 混合了FilterTableViewCell() 和FilterTableViewCell2() 对象时,swift 才会发疯? 正如我已经说过的,当你执行后者时,Swift 会自动推断这是一个 UITableViewCell 对象数组,而 没有 具有这些属性。但是,当您执行前者时,Swift 会推断这是一个 FilterTableViewCell 数组,确实具有这些属性。这就是为什么拥有一个确实 具有这些属性的公共超类的解决方案将有助于消除编译器的担忧。基本上你只需要再次阅读我之前的评论。 当然,你也可以将 FilterTableViewCell 设为 FilterTableViewCell2 的超类(反之亦然),从而只使用两种类型。

以上是关于在表格视图中放置不同的自定义单元格类型的主要内容,如果未能解决你的问题,请参考以下文章

我想在单个表格视图中使用来自 nib 的两个不同的自定义单元格。两个原型电池具有不同的高度

表格视图中的自定义单元格未显示?

在表格视图中访问我的自定义单元格

如何为 MkAnnotation View 自定义视图,就像表格视图的自定义单元格一样?

如何在 UIMenuController 的自定义操作中获取点击的表格视图单元格

故事板中的自定义表格视图单元格布局似乎无效