Table 视图数据被覆盖

Table View Data is overridden

我有一个 UITableView。它的单元格包含一个将显示问题的标签、一个是按钮和一个否按钮。目的是逐一查看问题。 首先我调用 API 来获取 viewDidLoad 方法中的问题:

override func viewDidLoad() {
        super.viewDidLoad()
        tableView.allowsSelection = false

        getQuestions(baseComplainID: "1") { (questions, error) in
            self.questions = questions
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
    }

在 cellForRowAt 方法中,我将它们一一显示:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell else {
            fatalError("Fatal Error")
        }
        cell.yesButton.isHidden = false
        cell.noButton.isHidden = false

        if indexPath.row + 1 == displayNumber {
            cell.questionLabel.text = questions[indexPath.row].question_name
        } else {
            cell.yesButton.isHidden = true
            cell.noButton.isHidden = true
        }

        cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
        cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)

        return cell
    }

这是在单击是或否时执行的操作:

@objc func action(sender: UIButton){
        let indexPath = self.tableView.indexPathForRow(at: sender.convert(CGPoint.zero, to: self.tableView))
        let cell = tableView.cellForRow(at: indexPath!) as? TableViewCell
        cell?.yesButton.isEnabled = false
        cell?.noButton.isEnabled = false

        if sender == cell?.yesButton {
            sender.setTitleColor(.black, for: .normal)
            sender.backgroundColor = .green
        } else {
            sender.setTitleColor(.black, for: .normal)
            sender.backgroundColor = .green
        }

        displayNumber += 1
        self.tableView.reloadData()
    }

这里我只是改变了按钮的背景颜色,并增加了显示数字来显示下一个问题。

除了当我滚动时,所有这些都很完美,数据被覆盖,有时我发现问题标签是空的,问题会相互替换。我知道由于单元的可重用性,这是正常的,但我不知道如何修复它。

有什么建议吗?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell else {
            fatalError("Fatal Error")
        }
        cell.yesButton.isHidden = false
        cell.noButton.isHidden = false

        if indexPath.row + 1 == displayNumber {
            cell.questionLabel.text = questions[indexPath.row].question_name
        } else {
            cell.yesButton.isHidden = true
            cell.noButton.isHidden = true
        }

        cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
        cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)

        return cell
    }

我觉得您的问题出在 cellForRowAt 函数中。

你已经写好了

if indexPath.row + 1 == displayNumber { your code here }

但我不确定您为什么需要这个。

你应该在 cellForRowAt 中做这样的事情

let data = self.questions
data = data[indexPath.row]
cell.questionLabel.text = data.question_name

你不应该在你的 indexPath.row

中加 1

您将需要跟踪每个单元格的是和否。我会将枚举与您的问题一起添加到另一个数据结构中。你的主要问题是你只是在跟踪你的问题。您还需要跟踪您的答案。这样,当您加载一个单元格时,您可以在 cellForRow(at:)

中为每个按钮配置您想要的颜色
struct QuestionAndAnswer {
    enum Answer {
        case yes
        case no
        case nada
    }

    var question: Question
    var answer: Answer
}

并尽量不要在按下按钮时重新加载整个 tableView。 tableView.reloadData() 很昂贵并且会分散用户的注意力。您应该只重新加载按下按钮时更改的行。

在您的单元格上添加回调,以便您知道相应按钮属于哪个单元格。请注意我们如何在 onYes 和 onNo 回调中跟踪您的 "yes" 或 "no" 选择然后立即重新加载下面的行。当行被重新加载时,我们终于知道按钮的颜色了。

class AnswerCell: UITableViewCell {
    @IBOutlet weak var yesButton: UIButton!
    @IBOutlet weak var noButton: UIButton!

    var onYes: (() -> Void)) = {}
    var onNo: (() -> Void)) = {}
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // ...

    cell.yesButton.backgroundColor = qAndA.answer == .yes ? .green : .white
    cell.noButton.backgroundColor = qAndA.answer == .no ? .green : .white

    cell.onYes = {
        questionsAndAnswers[indexPath.row].answer = .yes
        tableView.reloadRows(at: [indexPath], with: .fade)
    }
    cell.onNo = {
        questionsAndAnswers[indexPath.row].answer = .no
        tableView.reloadRows(at: [indexPath], with: .fade)
    }

    // ...
}

好吧,假设您有 10 个问题,那么一个非常简单的解决方法是声明一个包含 10 个元素的新数组,如下所示

var questionIsLoaded = Array(repeating:true , count 10)

上一行将声明一个包含 10 个元素的数组,每个元素都是 bool,在我们的例子中是 true

然后声明一个函数来处理问题是否加载如下,所以如果问题被加载,带有它的 indexPath 的问题应该被标记为 true,因此,是和否按钮应该隐藏在其他地方,按钮应该显示

func handleQuestionIfLoaded(cell:yourCellType, indexPath:IndexPath) {
if questionIsLoaded[indexPath.row] , indexPath.row + 1 == displayNumber { {
questionIsLoaded[indexPath.row] = false
            cell.questionLabel.text = questions[indexPath.row].question_name
            cell.yesButton.isHidden = questionIsLoaded[indexPath.row]
            cell.noButton.isHidden = questionIsLoaded[indexPath.row]
        } else {
            cell.yesButton.isHidden = questionIsLoaded[indexPath.row]
            cell.noButton.isHidden = questionIsLoaded[indexPath.row]
        }

        cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
        cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}

然后将cellForRowAt的body替换成上面的函数,那么你的action函数就如下

@objc func action(sender: UIButton){
        let indexPath = self.tableView.indexPathForRow(at: sender.convert(CGPoint.zero, to: self.tableView))
        let cell = tableView.cellForRow(at: indexPath!) as? TableViewCell
        cell?.yesButton.isEnabled = questionIsLoaded[indexPath.row]
        cell?.noButton.isEnabled = questionIsLoaded[indexPath.row]

    if sender == cell?.yesButton {
        sender.setTitleColor(.black, for: .normal)
        sender.backgroundColor = .green
    } else {
        sender.setTitleColor(.black, for: .normal)
        sender.backgroundColor = .green
    }

    displayNumber += 1
    self.tableView.reloadData()
}

现在,您的单元格依赖于一个外部依赖项,即您之前声明的数组,这意味着当单元格出队时,将根据问题是否加载通过询问数组的元素来重新使用它们如果元素为真或假,首先在特定的 indexPath