UICollectionView首尾单元格按钮选中状态同步异常问题求助
问题描述
我有一个UICollectionView,每个单元格内包含4个按钮。当选中第一个单元格中的任一按钮时,最后一个单元格的对应按钮也会被选中,反之亦然,该问题仅出现在首尾单元格。
ViewController 相关代码
class QuizViewController: UIViewController { private lazy var collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.register(QuestionView.self, forCellWithReuseIdentifier: "questionCell") collectionView.showsHorizontalScrollIndicator = false collectionView.isPagingEnabled = true collectionView.bounces = false collectionView.delegate = self collectionView.dataSource = self return collectionView }() } extension QuizViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { self.questionsIndex = indexPath.row } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return questions.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "questionCell", for: indexPath) as! QuestionView cell.configure(with: questions[indexPath.row]) cell.delegate = self return cell } }
UICollectionViewCell 相关代码
class QuestionView: UICollectionViewCell { private var question: Question { didSet { questionLabel.text = question.question answerButton1.setTitle(question.answers[0], for: .normal) answerButton2.setTitle(question.answers[1], for: .normal) answerButton3.setTitle(question.answers[2], for: .normal) answerButton4.setTitle(question.answers[3], for: .normal) } } private lazy var answerButton1: CustomAnswerButton = { let button = CustomAnswerButton(answerLabel: "") button.addTarget(self, action: #selector(didTapAnswerButton), for: .touchUpInside) return button }() // 另外三个按钮命名类似answerButton2、answerButton3、answerButton4 public func configure(with question: Question) { self.question = question } @objc private func didTapAnswerButton(_ sender: UIButton) { answerButton1.backgroundColor = .white answerButton2.backgroundColor = .white answerButton3.backgroundColor = .white answerButton4.backgroundColor = .white if(sender.isSelected) { sender.isSelected = false delegate?.didUnselectAnswer() } else { if(answerButton1.isSelected || answerButton2.isSelected || answerButton3.isSelected || answerButton4.isSelected) { delegate?.didUnselectAnswer() } answerButton1.isSelected = false answerButton2.isSelected = false answerButton3.isSelected = false answerButton4.isSelected = false sender.isSelected = true sender.backgroundColor = UIColor(red: 162/255, green: 210/255, blue: 255/255, alpha: 1) delegate?.didSelectAnswer() } } }
问题原因及解决办法
原因
UICollectionView的单元格复用机制导致状态串用。当滚动到边界时,首尾单元格会被复用,而当前代码没有将按钮的选中状态与对应问题的数据源绑定,复用后按钮状态没有正确重置,从而出现首尾状态同步的问题。
解决步骤
1. 扩展Question模型,增加选中状态记录
给Question结构体/类添加字段,存储当前问题选中的答案索引:
struct Question { let question: String let answers: [String] var selectedAnswerIndex: Int? // 记录选中的答案索引,nil表示未选中 }
2. 优化cell的configure方法,恢复对应状态
修改QuestionView的configure方法,在设置题目和答案的同时,根据数据源恢复按钮的选中状态:
public func configure(with question: Question) { self.question = question // 先重置所有按钮状态 resetAllButtons() // 根据问题的选中状态设置对应按钮 guard let selectedIndex = question.selectedAnswerIndex else { return } let targetButton: UIButton switch selectedIndex { case 0: targetButton = answerButton1 case 1: targetButton = answerButton2 case 2: targetButton = answerButton3 case 3: targetButton = answerButton4 default: return } targetButton.isSelected = true targetButton.backgroundColor = UIColor(red: 162/255, green: 210/255, blue: 255/255, alpha: 1) } // 提取重置按钮状态的复用方法 private func resetAllButtons() { [answerButton1, answerButton2, answerButton3, answerButton4].forEach { button in button.isSelected = false button.backgroundColor = .white } }
3. 修改按钮点击事件,同步更新数据源
在按钮点击方法中,更新对应Question的选中状态,确保状态持久化到数据源:
@objc private func didTapAnswerButton(_ sender: UIButton) { resetAllButtons() let buttonIndex: Int switch sender { case answerButton1: buttonIndex = 0 case answerButton2: buttonIndex = 1 case answerButton3: buttonIndex = 2 case answerButton4: buttonIndex = 3 default: return } if sender.isSelected { sender.isSelected = false question.selectedAnswerIndex = nil delegate?.didUnselectAnswer() } else { sender.isSelected = true sender.backgroundColor = UIColor(red: 162/255, green: 210/255, blue: 255/255, alpha: 1) question.selectedAnswerIndex = buttonIndex delegate?.didSelectAnswer() } }
4. 确保数据源可变
保证QuizViewController中的questions数组是可变的,这样修改后的状态才能被保存:
var questions: [Question] = [] // 使用var声明,而非let
经过以上修改,每个单元格的按钮状态都会与对应问题的数据源绑定,复用单元格时会正确恢复状态,解决首尾单元格状态串用的问题。
内容的提问来源于stack exchange,提问作者user13965197
相关产品推荐
相关产品推荐

