如何实现TableView行切换时UISwitch的互斥选中控制
Hey there! Let's work through this to get your desired behavior—where tapping a table row turns its UISwitch green (on), and automatically turns off the switch from the previously selected row. Your current code has a few logic gaps and misused variables, so let's clean this up step by step.
First, Simplify Your State-Tracking Variables
Instead of using previousSwitch (a Bool) and rowNumber, we only need one optional integer to track which row is currently selected. This makes the logic way clearer:
// Track the currently selected row (nil means no row is selected) var selectedRow: Int?
Update cellForRowAt to Set Switch States Correctly
Your original cell configuration logic was tangled. We'll simplify it to directly set the switch's isOn state based on whether the current row is the selected one:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // Replace "CustomCell" with your actual cell identifier, and "YourCustomCell" with your cell class guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as? YourCustomCell else { return UITableViewCell() } // Set the switch state: on if this is the selected row, off otherwise cell.answerSwitch.isOn = (indexPath.row == selectedRow) // Optional: Handle direct switch taps to sync with row selection cell.answerSwitch.removeTarget(self, action: nil, for: .valueChanged) cell.answerSwitch.addTarget(self, action: #selector(switchToggled(_:)), for: .valueChanged) return cell }
Fix didSelectRowAt to Manage Selection State
Your original method had a missing parenthesis (tableView.reloadData instead of tableView.reloadData()) and overcomplicated logic with the Bool flag. Here's the corrected version, using efficient partial table reloads:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // If the tapped row is already selected, deselect it if selectedRow == indexPath.row { selectedRow = nil tableView.reloadRows(at: [indexPath], with: .automatic) } else { // Store the previous selected row to refresh it let previousIndexPath = selectedRow.map { IndexPath(row: $0, section: 0) } // Update to the new selected row selectedRow = indexPath.row // Refresh only the affected rows (previous + current) instead of the whole table var rowsToReload = [indexPath] if let prevPath = previousIndexPath { rowsToReload.append(prevPath) } tableView.reloadRows(at: rowsToReload, with: .automatic) } }
Optional: Handle Direct Switch Taps
To make sure users can't tap the switch independently of the row (which would break your single-selection rule), add this handler:
@objc private func switchToggled(_ sender: UISwitch) { // Get the cell that contains the tapped switch guard let cell = sender.superview?.superview as? YourCustomCell, let indexPath = tableView.indexPath(for: cell) else { return } if sender.isOn { // If the switch is turned on, set this row as the selected one selectedRow = indexPath.row tableView.reloadData() // Refresh all rows to turn off other switches } else { // If turned off, clear the selection selectedRow = nil } }
Key Notes
- Using
selectedRow: Int?keeps your state tracking simple and avoids confusing Bool flags. - Partial reloads (
reloadRows(at:with:)) are more performant than reloading the entire table. - Handling switch taps ensures your UI stays consistent, no matter how the user interacts with the control.
内容的提问来源于stack exchange,提问作者J.Doe2

