Swift中TableView单元格选取消选时selectedNamesArray不匹配问题排查
在Swift开发中,通过ViewController内的按钮实现UITableView单元格的选中与取消选中功能:
- 初始时
selectedRows数组已存储选中的单元格ID,页面加载后单元格选中状态显示正常 - 但
selectedNamesArray与selectedRows的索引始终不匹配,初始填充阶段就存在该问题 - 进行选中/取消选中操作后,
selectedRows的值正确,但selectedNamesArray的名称仍对应错误
相关代码及输出示例如下:
class ViewController2: UIViewController { @IBOutlet weak var tableView: UITableView! var selectedRows = [32, 38, 89, 11] var selectedNamesArray: [String] = [] var arrayModel: [SampleModel] = [SampleModel(id: 32, name: "Apple"), SampleModel(id: 38, name: "Smasung"), SampleModel(id: 22, name: "Black berry"), SampleModel(id: 76, name: "Nokia"), SampleModel(id: 12, name: "Real me"), SampleModel(id: 75, name: "Lenova"), SampleModel(id: 36, name: "Pocco"), SampleModel(id: 11, name: "Motorola"), SampleModel(id: 37, name: "Infinix"), SampleModel(id: 88, name: "China"), SampleModel(id: 89, name: "Jio") ] override func viewDidLoad() { super.viewDidLoad() if !selectedRows.isEmpty{ selectedNamesArray = arrayModel .filter { selectedRows.contains($0.id) } .map { $0.name } } print("existing names: \(selectedNamesArray) \n and ids: \(selectedRows)") } } extension ViewController2: UITableViewDelegate, UITableViewDataSource{ func numberOfSections(in tableView: UITableView) -> Int { 2 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return arrayModel.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableviewCell1", for: indexPath) as? TestTableviewCell1 cell?.selectionStyle = .none cell?.titleLbl?.text = arrayModel[indexPath.row].name cell?.checkBtn.tag = indexPath.row cell?.isChecked = selectedRows.contains(arrayModel[indexPath.row].id) cell?.buttonAction = { [self] sender in if selectedRows.contains(arrayModel[indexPath.row].id) { if let indexToRemove = selectedRows.firstIndex(of: arrayModel[indexPath.row].id) { selectedRows.remove(at: indexToRemove) if indexToRemove < selectedNamesArray.count { selectedNamesArray.remove(at: indexToRemove) // Remove corresponding name } cell?.isChecked = false } } else { selectedRows.append(arrayModel[indexPath.row].id) selectedNamesArray.append(arrayModel[indexPath.row].name) // Add the corresponding name cell?.isChecked = true } print("Selected rows: \(selectedRows)") print("Selected names: \(selectedNamesArray)") } return cell! } } class TestTableviewCell1: UITableViewCell{ @IBOutlet weak var titleLbl: UILabel! @IBOutlet weak var checkBtn: UIButton! var isChecked = false{ didSet{ checkBtn.backgroundColor = isChecked ? .blue : .green } } var buttonAction: ((_ sender: AnyObject) -> Void)? @IBAction func buttonPressed(_ sender: UIButton) { self.buttonAction?(sender) } } struct SampleModel{ var id: Int var name: String }
输出示例
- 初始输出:
existing names: ["Apple", "Smasung", "Motorola", "Jio"] and ids: [32, 38, 89, 11] // 两者数组值索引不匹配
- 操作后输出:
Selected rows: [32, 38, 89, 36] Selected names: ["Apple", "Smasung", "Motorola", "Pocco"] // 索引再次不匹配
初始填充顺序不匹配:
初始化selectedNamesArray时,arrayModel.filter会按照arrayModel本身的顺序筛选元素,而selectedRows的顺序是[32,38,89,11],但arrayModel中ID为89的元素在最后一位,ID为11的元素在第7位,因此筛选后的名称顺序是["Apple", "Smasung", "Motorola", "Jio"],和selectedRows的ID顺序完全对应不上,直接导致索引不匹配。操作时同步逻辑错误:
取消选中时,通过selectedRows的索引删除selectedNamesArray的元素,但两个数组顺序本就不一致,删除位置自然错误;选中时直接append名称,会延续这种顺序不匹配的问题。双数组维护的设计缺陷:
同时维护selectedRows(ID数组)和selectedNamesArray(名称数组)两个独立数据源属于冗余存储,极易出现同步不一致的问题,这是根本隐患。
方案一:移除selectedNamesArray,动态获取名称(推荐)
不需要单独维护名称数组,当需要获取选中名称时,直接通过selectedRows从arrayModel中匹配获取,从根源避免同步问题。
修改后的核心代码:
// 移除selectedNamesArray变量 // var selectedNamesArray: [String] = [] override func viewDidLoad() { super.viewDidLoad() // 初始化时不再填充名称数组,需要时动态获取 print("existing names: \(getSelectedNames()) \n and ids: \(selectedRows)") } // 添加获取选中名称的方法 func getSelectedNames() -> [String] { return selectedRows.compactMap { id in arrayModel.first(where: { $0.id == id })?.name } } // 单元格操作逻辑中移除名称数组的维护 cell?.buttonAction = { [self] sender in let currentId = arrayModel[indexPath.row].id if let indexToRemove = selectedRows.firstIndex(of: currentId) { selectedRows.remove(at: indexToRemove) cell?.isChecked = false } else { selectedRows.append(currentId) cell?.isChecked = true } print("Selected rows: \(selectedRows)") print("Selected names: \(getSelectedNames())") }
方案二:强制同步两个数组的顺序(如需保留名称数组)
如果必须保留selectedNamesArray,需要确保它和selectedRows的顺序完全一致:
- 初始化时按
selectedRows的顺序匹配名称:
override func viewDidLoad() { super.viewDidLoad() if !selectedRows.isEmpty{ // 按selectedRows的顺序逐个查找对应名称 selectedNamesArray = selectedRows.compactMap { id in arrayModel.first(where: { $0.id == id })?.name } } print("existing names: \(selectedNamesArray) \n and ids: \(selectedRows)") }
- 操作时严格同步两个数组的增删:
cell?.buttonAction = { [self] sender in let currentId = arrayModel[indexPath.row].id let currentName = arrayModel[indexPath.row].name if let indexToRemove = selectedRows.firstIndex(of: currentId) { selectedRows.remove(at: indexToRemove) // 同步删除对应索引的名称 if indexToRemove < selectedNamesArray.count { selectedNamesArray.remove(at: indexToRemove) } cell?.isChecked = false } else { selectedRows.append(currentId) // 同步添加名称到对应位置 selectedNamesArray.append(currentName) cell?.isChecked = true } print("Selected rows: \(selectedRows)") print("Selected names: \(selectedNamesArray)") }
修改后效果
初始输出会变成:
existing names: ["Apple", "Smasung", "Jio", "Motorola"] and ids: [32, 38, 89, 11]
此时两个数组的索引完全匹配,后续操作也会保持同步。
内容的提问来源于stack exchange,提问作者Swift

