I'm creating an application in which I need the users to fill out a number of inputs in a UITableViewCell
, kinda like a form. When the user taps on the done button, I need to collect those inputs so I can run some calculations and output them on another view controller
我正在创建一个应用程序,我需要用户在UITableViewCell中填写一些输入,有点像表单。当用户点击完成按钮时,我需要收集这些输入,这样我就可以运行一些计算并将它们输出到另一个视图控制器上
Here is the method I used to collect those inputs:
这是我用来收集这些输入的方法:
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)
}
The problem I'm having is when the cell is out of view, the user's input is cleared from the memory. Here are two screenshots to illustrate my point:
我遇到的问题是当单元格不在视图范围内时,用户的输入将从内存中清除。这里有两个截图来说明我的观点:
As you can see, when all the cells appears, the done button managed to store all the inputs from the user, evidently from the console print. However, if the cells are out of view, the inputs from area/m2 are set to nil:
如您所见,当所有单元格出现时,完成按钮设法存储来自用户的所有输入,显然来自控制台打印。但是,如果单元格不在视图范围内,则面积/ m2的输入将设置为nil:
The solution that came to mind was I shouldn't use a dequeue-able cell as I do want the cell to be in memory when it is out-of-view, but many of the stackover community strong against this practice. How should I solve this problem? Thanks!
想到的解决方案是我不应该使用一个可以出列的单元格,因为我确实希望单元格在内存时处于内存状态,但许多堆栈社区强烈反对这种做法。我该如何解决这个问题?谢谢!
UPDATE
UPDATE
Code for cellForRow(at: IndexPath)
cellForRow的代码(at:IndexPath)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let node = node else {
return UITableViewCell()
}
let cellArray = TableViewCells.getTableViewCell(ceilingType: node.ceilingSelected, moduleType: node.moduleSelected)
switch cellArray[indexPath.row].cellType {
case .numericInput :
let cell = tableView.dequeueReusableCell(withIdentifier: "numericCell", for: indexPath) as! NumericInputTableViewCell
cell.numericTitleLabel.text = cellArray[indexPath.row].title
return cell
case .booleanInput :
let cell = tableView.dequeueReusableCell(withIdentifier: "booleanCell", for: indexPath) as! BooleanInputTableViewCell
cell.booleanTitleLabel.text = cellArray[indexPath.row].title
return cell
}
}
}
My two custom cells
我的两个定制单元格
NumericInputTableViewCell
NumericInputTableViewCell
class NumericInputTableViewCell: UITableViewCell {
@IBOutlet weak var numericTitleLabel: UILabel!
@IBOutlet weak var userInputTextField: UITextField!
}
BooleanInputTableViewCell
BooleanInputTableViewCell
class BooleanInputTableViewCell: UITableViewCell {
@IBOutlet weak var booleanTitleLabel: UILabel!
@IBOutlet weak var booleanToggleSwitch: UISwitch!
}
Any takers?
任何接受者?
2 个解决方案
#1
1
I agree with the other contributors. The cells should not be used for data storage. You should consider another approach (like the one HMHero suggests).
我同意其他贡献者的观点。不应将单元用于数据存储。您应该考虑另一种方法(如HMHero建议的那种方法)。
But, as your question was also about how to access a UITableViewCell before it is removed, there is a method in UITableViewDelegate that you can use for that:
但是,由于您的问题还在于如何在删除之前访问UITableViewCell,因此UITableViewDelegate中有一个方法可用于此:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// do something with the cell before it gets deallocated
}
This method tells the delegate that the specified cell was removed from the table. So it gives a last chance to do something with that cell before it disappears.
此方法告诉委托指定的单元格已从表中删除。所以它给了最后一次机会在它消失之前对那个细胞做些什么。
#2
0
Because of table view reuses its cells, usually, it's not a good idea if your data depends on some components from the table view cell. Rather, it should be the other way around. Your table view data always drive it's table view cell's component even before any user input data is provided in your case.
由于表视图重用其单元格,通常,如果您的数据依赖于表格视图单元格中的某些组件,则不是一个好主意。相反,它应该是相反的方式。即使在您的案例中提供任何用户输入数据之前,您的表视图数据也总是驱动它的表视图单元的组件。
-
Initial Data - your should already have somewhere in your code. I created my own from your provided code
初始数据 - 您的代码应该已存在。我从您提供的代码中创建了自己的代码
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)
-
Example
例
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") } }
-
In View Controller, you should update your original data source so when you scroll the table view, the data source privide right infomation.
在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 }
In short, try to have a single data source for table view and use the delegate to pass the updated data in the cell back to the data source.
简而言之,尝试为表视图提供单个数据源,并使用委托将单元中的更新数据传递回数据源。
Please disregard anything that has to do with layout. I didn't use the storyboard to test your requirements.
请忽略任何与布局有关的事情。我没有使用故事板来测试您的要求。
#1
1
I agree with the other contributors. The cells should not be used for data storage. You should consider another approach (like the one HMHero suggests).
我同意其他贡献者的观点。不应将单元用于数据存储。您应该考虑另一种方法(如HMHero建议的那种方法)。
But, as your question was also about how to access a UITableViewCell before it is removed, there is a method in UITableViewDelegate that you can use for that:
但是,由于您的问题还在于如何在删除之前访问UITableViewCell,因此UITableViewDelegate中有一个方法可用于此:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// do something with the cell before it gets deallocated
}
This method tells the delegate that the specified cell was removed from the table. So it gives a last chance to do something with that cell before it disappears.
此方法告诉委托指定的单元格已从表中删除。所以它给了最后一次机会在它消失之前对那个细胞做些什么。
#2
0
Because of table view reuses its cells, usually, it's not a good idea if your data depends on some components from the table view cell. Rather, it should be the other way around. Your table view data always drive it's table view cell's component even before any user input data is provided in your case.
由于表视图重用其单元格,通常,如果您的数据依赖于表格视图单元格中的某些组件,则不是一个好主意。相反,它应该是相反的方式。即使在您的案例中提供任何用户输入数据之前,您的表视图数据也总是驱动它的表视图单元的组件。
-
Initial Data - your should already have somewhere in your code. I created my own from your provided code
初始数据 - 您的代码应该已存在。我从您提供的代码中创建了自己的代码
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)
-
Example
例
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") } }
-
In View Controller, you should update your original data source so when you scroll the table view, the data source privide right infomation.
在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 }
In short, try to have a single data source for table view and use the delegate to pass the updated data in the cell back to the data source.
简而言之,尝试为表视图提供单个数据源,并使用委托将单元中的更新数据传递回数据源。
Please disregard anything that has to do with layout. I didn't use the storyboard to test your requirements.
请忽略任何与布局有关的事情。我没有使用故事板来测试您的要求。