quantityTextfields 获取幽灵值(用户未在文本字段中输入的值)即使仅更改一个文本字段,我认为这是因为 TableView 单元格如何重用,但如何解决它
下面是quantityTextFields的声明
private var quantityTextFields: [Int:[String: UITextField]] = [:] // Map material codes to text fields
extension CompetitorSaleThruVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
selectedMaterials.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tblView.dequeueReusableCell(withIdentifier: "competitorCell", for: indexPath) as! CompetitorCell
let material = selectedMaterials[indexPath.row]
cell.quantityTextField.text = ""
cell.label.text = material.value
//quantityTextFields[material.value] = cell.quantityTextField
quantityTextFields[indexPath.row] = [material.value:cell.quantityTextField]
// cell.quantityTextField.text = textFieldValues[indexPath.row]
cell.quantityTextField.tag = indexPath.row
cell.selectionStyle = .none
cell.quantityTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
cell.quantityTextField.delegate = self
return cell
if navigatedFromCompetitorDisplayCell == true && material.value == competitorMaterial {
cell.quantityTextField.text = String(compSaleQuantity)
}
}
}
单元格被重用...尝试跟踪单元格中的 UI 元素(例如文本字段)并按照您的方式处理其更改很容易出现错误和“不匹配”。
更好的方法是在单元格类中处理文本字段编辑,然后使用“回调”闭包让控制器知道该字段已被编辑。
因此,我们向单元类添加这样的闭包:
class CompetitorCell: UITableViewCell, UITextFieldDelegate {
// "callback" closure
var quantityEdited: ((CompetitorCell, Int) -> ())?
在单元类中,我们添加目标并处理编辑:
// in cell init
{
// action when field is edited
quantityTextField.addTarget(self, action: #selector(edited(_:)), for: .editingChanged)
}
@objc func edited(_ sender: UITextField) {
let str = sender.text ?? ""
// execute the "callback" closure
self.quantityEdited?(self, Int(str) ?? 0)
}
然后,在您的控制器中
cellForRowAt
:
// set the "callback" closure
cell.quantityEdited = { [weak self] theCell, theValue in
// safely unwrap optionals
guard let self = self else { return }
guard let indexPath = self.tableView.indexPath(for: theCell) else { return }
// update our data
self.materials[indexPath.row].quantity = theValue
}
这是一个完整的示例 - 没有
@IBOutlet
或 @IBAction
连接...只需将空白视图控制器的自定义类分配给 CompTableVC
简单数据结构
struct Material {
var name: String = ""
var quantity: Int = 0
}
单元格类 - 带有标签和文本字段
class CompetitorCell: UITableViewCell, UITextFieldDelegate {
// "callback" closure
var quantityEdited: ((CompetitorCell, Int) -> ())?
static let identifier: String = "CompetitorCell"
let label = UILabel()
let quantityTextField = UITextField()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
label.translatesAutoresizingMaskIntoConstraints = false
quantityTextField.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
contentView.addSubview(quantityTextField)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
label.heightAnchor.constraint(equalToConstant: 42.0),
quantityTextField.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 8.0),
quantityTextField.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
quantityTextField.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
quantityTextField.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
quantityTextField.borderStyle = .roundedRect
quantityTextField.keyboardType = .numberPad
// action when field is edited
quantityTextField.addTarget(self, action: #selector(edited(_:)), for: .editingChanged)
// so we can see the framing
label.backgroundColor = .cyan
quantityTextField.backgroundColor = .yellow
}
@objc func edited(_ sender: UITextField) {
let str = sender.text ?? ""
// execute the "callback" closure
self.quantityEdited?(self, Int(str) ?? 0)
}
}
示例控制器类
class CompTableVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
var materials: [Material] = []
let tableView = UITableView()
let totalLabel = UILabel()
var doneButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
var cfg = UIButton.Configuration.filled()
cfg.title = "Done"
doneButton = UIButton(configuration: cfg, primaryAction: UIAction() { _ in
self.view.endEditing(true)
})
totalLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(totalLabel)
doneButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(doneButton)
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
totalLabel.centerYAnchor.constraint(equalTo: doneButton.centerYAnchor, constant: 0.0),
totalLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
totalLabel.heightAnchor.constraint(equalTo: doneButton.heightAnchor),
doneButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
doneButton.leadingAnchor.constraint(equalTo: totalLabel.trailingAnchor, constant: 20.0),
doneButton.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
tableView.topAnchor.constraint(equalTo: doneButton.bottomAnchor, constant: 20.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
totalLabel.backgroundColor = .systemRed
totalLabel.textColor = .white
tableView.register(CompetitorCell.self, forCellReuseIdentifier: CompetitorCell.identifier)
tableView.dataSource = self
tableView.delegate = self
// let's create some sample data
materials = (0..<20).map {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
var m: Material = Material()
m.name = formatter.string(for: $0)!
m.quantity = 1
return m
}
// handle keyboard covering cells...
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
// only show "Done" button if editing is active
doneButton.isHidden = true
// update the "Total" label
updateTotal()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return materials.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CompetitorCell.identifier, for: indexPath) as! CompetitorCell
// set the label and text field
cell.label.text = "Material: " + materials[indexPath.row].name
let q = materials[indexPath.row].quantity
cell.quantityTextField.text = q > 0 ? "\(q)" : ""
// set the "callback" closure
cell.quantityEdited = { [weak self] theCell, theValue in
// safely unwrap optionals
guard let self = self else { return }
guard let indexPath = self.tableView.indexPath(for: theCell) else { return }
// update our data
self.materials[indexPath.row].quantity = theValue
// update "Total" label
self.updateTotal()
}
return cell
}
func updateTotal() {
let t = materials.reduce(0) { $0 + $1.quantity }
totalLabel.text = "Total: \(t)"
}
@objc func keyboardWillShow(notification: NSNotification) {
guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
// Get the size of the keyboard
let keyboardHeight = keyboardFrame.height
// Adjust the tableView's contentInset and scrollIndicatorInsets
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)
tableView.contentInset = contentInsets
tableView.scrollIndicatorInsets = contentInsets
// Scroll to the active text field if it's hidden by the keyboard
if let activeTextField = findActiveTextField(),
let cell = activeTextField.superview?.superview as? UITableViewCell,
let indexPath = tableView.indexPath(for: cell) {
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
}
self.doneButton.isHidden = false
}
@objc func keyboardWillHide(notification: NSNotification) {
// Reset the tableView's contentInset and scrollIndicatorInsets
let contentInsets = UIEdgeInsets.zero
tableView.contentInset = contentInsets
tableView.scrollIndicatorInsets = contentInsets
self.doneButton.isHidden = true
}
func findActiveTextField() -> UITextField? {
for cell in tableView.visibleCells {
if let textField = cell.contentView.subviews.compactMap({ $0 as? UITextField }).first(where: { $0.isFirstResponder }) {
return textField
}
}
return nil
}
}
运行时看起来像这样:
您将能够编辑每个单元格中的“数量”...它将更新数据源...并且您可以上下滚动而不会出现“幽灵值”问题。