Ios 如何在退出队列之前在UITableViewCell中保留用户输入

Ios 如何在退出队列之前在UITableViewCell中保留用户输入,ios,swift,uitableview,Ios,Swift,Uitableview,我正在创建一个应用程序,需要用户在UITableViewCell中填写大量输入,有点像表单。当用户点击“完成”按钮时,我需要收集这些输入,以便运行一些计算并在另一个视图控制器上输出它们 以下是我用来收集这些输入的方法: func doneButtonTapped() { var dict = [String: Any]() for rows in 0...TableViewCells.getTableViewCell(ceilingType: node.ceilingSele

我正在创建一个应用程序,需要用户在
UITableViewCell
中填写大量输入,有点像表单。当用户点击“完成”按钮时,我需要收集这些输入,以便运行一些计算并在另一个视图控制器上输出它们

以下是我用来收集这些输入的方法:

func doneButtonTapped() {

    var dict = [String: Any]()

    for rows in 0...TableViewCells.getTableViewCell(ceilingType: node.ceilingSelected, moduleType: node.moduleSelected).count {

        let ip = IndexPath(row: rows, section: 0)

        let cells = tableView.cellForRow(at: ip)

        if let numericCell = cells as? NumericInputTableViewCell {

            if let text = numericCell.userInputTextField.text {

                dict[numericCell.numericTitleLabel.text!] = text
            }

        } else if let booleanCell = cells as? BooleanInputTableViewCell {

            let booleanSelection = booleanCell.booleanToggleSwitch.isOn
            dict[booleanCell.booleanTitleLabel.text!] = booleanSelection
        }
    }

    let calculator = Calculator(userInputDictionary: dict, ceiling_type: node.ceilingSelected)
}
我遇到的问题是,当单元格看不见时,用户的输入会从内存中清除。下面是两个屏幕截图来说明我的观点:

如您所见,当所有单元格出现时,“完成”按钮设法存储来自用户的所有输入,显然是来自控制台打印。但是,如果单元格不可见,则area/m2的输入设置为nil:

我想到的解决方案是,我不应该使用一个可出列的单元格,因为我确实希望该单元格在看不见的情况下位于内存中,但stackover社区的许多人强烈反对这种做法。我应该如何解决这个问题?谢谢

更新

cellForRow的代码(at:indepath)

我的两个自定义单元格

NumericInputTableViewCell

class NumericInputTableViewCell: UITableViewCell {

    @IBOutlet weak var numericTitleLabel: UILabel!

    @IBOutlet weak var userInputTextField: UITextField!

}
class BooleanInputTableViewCell: UITableViewCell {

    @IBOutlet weak var booleanTitleLabel: UILabel!

    @IBOutlet weak var booleanToggleSwitch: UISwitch!

}
booleanInputableViewCell

class NumericInputTableViewCell: UITableViewCell {

    @IBOutlet weak var numericTitleLabel: UILabel!

    @IBOutlet weak var userInputTextField: UITextField!

}
class BooleanInputTableViewCell: UITableViewCell {

    @IBOutlet weak var booleanTitleLabel: UILabel!

    @IBOutlet weak var booleanToggleSwitch: UISwitch!

}

任何接受者?

由于表视图重用其单元格,通常,如果数据依赖于表视图单元格中的某些组件,这不是一个好主意。相反,它应该是另一种方式。即使在案例中提供任何用户输入数据之前,表视图数据始终驱动其表视图单元格的组件

  • 初始数据-您的代码中应该已经有了。我根据您提供的代码创建了自己的代码

        let data = CellData()
        data.title = "Troffer Light Fittin"
        data.value = false
    
        let data2 = CellData()
        data2.title = "Length Drop"
        data2.value = "0"
    
        cellData.append(data)
        cellData.append(data2)
    
  • 范例

    enum CellType {
        case numericInput, booleanInput
    }
    
    class CellData {
        var title: String?
        var value: Any?
    
        var cellType: CellType {
            if let _ = value as? Bool {
                return .booleanInput
            } else {
                return .numericInput
            }
        }
        }
    
    protocol DataCellDelegate: class {
        func didChangeCellData(_ cell: UITableViewCell)
    }
    
    class DataTableViewCell: UITableViewCell {
        var data: CellData?
        weak var delegate: DataCellDelegate?
    }
    
    class NumericInputTableViewCell: DataTableViewCell {
    
    let userInputTextField: UITextField = UITextField()
    
    override var data: CellData? {
        didSet {
            textLabel?.text = data?.title
            if let value = data?.value as? String {
                userInputTextField.text = value
            }
        }
    }
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    
        userInputTextField.addTarget(self, action: #selector(textDidChange(_:)), for: .editingChanged)
        contentView.addSubview(userInputTextField)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func textDidChange(_ textField: UITextField) {
        //update data and let the delegate know data is updated
        data?.value = textField.text
        delegate?.didChangeCellData(self)
    }
    //Disregard this part
    override func layoutSubviews() {
        super.layoutSubviews()
        textLabel?.frame.size.height = bounds.size.height / 2
        userInputTextField.frame = CGRect(x: (textLabel?.frame.origin.x ?? 10), y: bounds.size.height / 2, width: bounds.size.width - (textLabel?.frame.origin.x ?? 10), height: bounds.size.height / 2)
    }
    }
    
    class BooleanInputTableViewCell: DataTableViewCell {
    
    override var data: CellData? {
        didSet {
            textLabel?.text = data?.title
            if let value = data?.value as? Bool {
                booleanToggleSwitch.isOn = value
            }
        }
    }
    
    let booleanToggleSwitch = UISwitch(frame: .zero)
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    
        booleanToggleSwitch.addTarget(self, action: #selector(toggled), for: .valueChanged)
        booleanToggleSwitch.isOn = true
        accessoryView = booleanToggleSwitch
        accessoryType = .none
        selectionStyle = .none
    
    }
    
    func toggled() {
        //update data and let the delegate know data is updated
        data?.value = booleanToggleSwitch.isOn
        delegate?.didChangeCellData(self)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    }
    
  • 在View Controller中,您应该更新原始数据源,以便在滚动表视图时,数据源将保留右侧信息

    func didChangeCellData(_ cell: UITableViewCell) {
        if let cell = cell as? DataTableViewCell {
            for data in cellData {
                if let title = data.title, let titlePassed = cell.data?.title, title == titlePassed {
                        data.value = cell.data?.value
                }
            }
        }
    
        for data in cellData {
            print("\(data.title) \(data.value)")
        }
    }
    
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return cellData.count
        }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let data = cellData[indexPath.row]
        let cell: DataTableViewCell
        if data.cellType == .booleanInput {
            cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(BooleanInputTableViewCell.self), for: indexPath) as! BooleanInputTableViewCell
        } else {
            cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(NumericInputTableViewCell.self), for: indexPath) as! NumericInputTableViewCell
        }
        cell.data = cellData[indexPath.row]
        cell.delegate = self
        return cell
    }
    
简而言之,尝试为表视图使用单个数据源,并使用委托将单元格中更新的数据传递回数据源


请忽略任何与布局有关的内容。我没有使用故事板来测试您的需求。

因为表视图重用其单元格,通常,如果您的数据依赖于表视图单元格中的某些组件,这不是一个好主意。相反,它应该是另一种方式。即使在案例中提供任何用户输入数据之前,表视图数据始终驱动其表视图单元格的组件

  • 初始数据-您的代码中应该已经有了。我根据您提供的代码创建了自己的代码

        let data = CellData()
        data.title = "Troffer Light Fittin"
        data.value = false
    
        let data2 = CellData()
        data2.title = "Length Drop"
        data2.value = "0"
    
        cellData.append(data)
        cellData.append(data2)
    
  • 范例

    enum CellType {
        case numericInput, booleanInput
    }
    
    class CellData {
        var title: String?
        var value: Any?
    
        var cellType: CellType {
            if let _ = value as? Bool {
                return .booleanInput
            } else {
                return .numericInput
            }
        }
        }
    
    protocol DataCellDelegate: class {
        func didChangeCellData(_ cell: UITableViewCell)
    }
    
    class DataTableViewCell: UITableViewCell {
        var data: CellData?
        weak var delegate: DataCellDelegate?
    }
    
    class NumericInputTableViewCell: DataTableViewCell {
    
    let userInputTextField: UITextField = UITextField()
    
    override var data: CellData? {
        didSet {
            textLabel?.text = data?.title
            if let value = data?.value as? String {
                userInputTextField.text = value
            }
        }
    }
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    
        userInputTextField.addTarget(self, action: #selector(textDidChange(_:)), for: .editingChanged)
        contentView.addSubview(userInputTextField)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func textDidChange(_ textField: UITextField) {
        //update data and let the delegate know data is updated
        data?.value = textField.text
        delegate?.didChangeCellData(self)
    }
    //Disregard this part
    override func layoutSubviews() {
        super.layoutSubviews()
        textLabel?.frame.size.height = bounds.size.height / 2
        userInputTextField.frame = CGRect(x: (textLabel?.frame.origin.x ?? 10), y: bounds.size.height / 2, width: bounds.size.width - (textLabel?.frame.origin.x ?? 10), height: bounds.size.height / 2)
    }
    }
    
    class BooleanInputTableViewCell: DataTableViewCell {
    
    override var data: CellData? {
        didSet {
            textLabel?.text = data?.title
            if let value = data?.value as? Bool {
                booleanToggleSwitch.isOn = value
            }
        }
    }
    
    let booleanToggleSwitch = UISwitch(frame: .zero)
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    
        booleanToggleSwitch.addTarget(self, action: #selector(toggled), for: .valueChanged)
        booleanToggleSwitch.isOn = true
        accessoryView = booleanToggleSwitch
        accessoryType = .none
        selectionStyle = .none
    
    }
    
    func toggled() {
        //update data and let the delegate know data is updated
        data?.value = booleanToggleSwitch.isOn
        delegate?.didChangeCellData(self)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    }
    
  • 在View Controller中,您应该更新原始数据源,以便在滚动表视图时,数据源将保留右侧信息

    func didChangeCellData(_ cell: UITableViewCell) {
        if let cell = cell as? DataTableViewCell {
            for data in cellData {
                if let title = data.title, let titlePassed = cell.data?.title, title == titlePassed {
                        data.value = cell.data?.value
                }
            }
        }
    
        for data in cellData {
            print("\(data.title) \(data.value)")
        }
    }
    
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return cellData.count
        }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let data = cellData[indexPath.row]
        let cell: DataTableViewCell
        if data.cellType == .booleanInput {
            cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(BooleanInputTableViewCell.self), for: indexPath) as! BooleanInputTableViewCell
        } else {
            cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(NumericInputTableViewCell.self), for: indexPath) as! NumericInputTableViewCell
        }
        cell.data = cellData[indexPath.row]
        cell.delegate = self
        return cell
    }
    
简而言之,尝试为表视图使用单个数据源,并使用委托将单元格中更新的数据传递回数据源


请忽略任何与布局有关的内容。我没有使用故事板来测试您的需求。

我同意其他贡献者的观点。这些单元格不应用于数据存储。你应该考虑另一种方法(如建议的那样)。 但是,由于您的问题也是关于如何在移除UITableViewCell之前访问它,因此在UITableViewDelegate中有一种方法可用于:

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // do something with the cell before it gets deallocated
}

此方法告诉委托指定的单元格已从表中删除。因此,它给了我们最后一次机会在细胞消失之前对其进行处理。

我同意其他贡献者的观点。这些单元格不应用于数据存储。你应该考虑另一种方法(如建议的那样)。 但是,由于您的问题也是关于如何在移除UITableViewCell之前访问它,因此在UITableViewDelegate中有一种方法可用于:

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // do something with the cell before it gets deallocated
}

此方法告诉委托指定的单元格已从表中删除。因此,在该单元格消失之前,它提供了最后一次对其进行操作的机会。

为什么不在用户填充字典时填充字典呢?然后,所有数据都存储在字典中,而不是表中(不推荐使用),使用委托将数据传递回dict(数据源)。由于单元格被重用,您不希望依赖单元格上的显示数据。请同意@GIJOW。换句话说,视图不应被用作数据模型的替代品。您只需为每个单元格生成并注册不同的reuseidenfitifer,这将确保下一次相同的单元格出列。另外,只要确保在存储单元时设置了相同的内容,单元逻辑就不会刷新该单元dequeued@GIJOW我在
didSelectRow
中尝试了这个方法,但没有成功,这意味着只有当用户没有点击文本字段时,才能存储输入,这是因为直接点击文本字段,文本字段将是第一个响应的字段,而不是注册TAP。为什么不在用户填写词典时填写词典?然后,所有数据都存储在字典中,而不是表中(不推荐使用),使用委托将数据传递回dict(数据源)。由于单元格被重用,您不希望依赖单元格上的显示数据。请同意@GIJOW。换句话说,视图不应被用作数据模型的替代品。您只需为每个单元格生成并注册不同的reuseidenfitifer,这将确保下一次相同的单元格出列。另外,只要确保在存储单元时设置了相同的内容,单元逻辑就不会刷新该单元dequeued@GIJOW我在
didSelectRow
中尝试了这个方法,但没有成功,这意味着只有当用户没有点击文本字段时,才能存储输入,这是因为直接点击文本字段,文本字段将是第一个响应的字段,而不是registerin