You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 17:50:34