Reputation: 535
I know there are many questions similar to mine, however many of them are outdated, and others did not work with my case.
I have a questionnaire in a UITableView
that contains 5 questions and 3 choices for each.
choice is a UIButton that changes image when clicked.
BUT when the first questions is answered, the fifth question gets answered as well!
Here is my tableView
methods:
var questions: [Question] = []
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let question = questions[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "choicesCell") as! ChoicesCell
cell.setQuestion(question: question)
cell.selectionStyle = .none
return cell
}
This is how I setup the images to choices buttons when they are selected:
@IBAction func answerOneTapped(_ sender: UIButton) {
delegate?.didTapAnswerOne()
print("answerOneTapped")
answerOneButton.setImage(#imageLiteral(resourceName: "ElipseSelected"), for: .normal)
answerTwoButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerThreeButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
}
@IBAction func answerTwoTapped(_ sender: UIButton) {
delegate?.didTapAnswerTwo()
print("answerTwoTapped")
answerOneButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerTwoButton.setImage(#imageLiteral(resourceName: "ElipseSelected"), for: .normal)
answerThreeButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
}
@IBAction func answerThreeTapped(_ sender: UIButton) {
delegate?.didTapAnswerThree()
print("answerThreeTapped")
answerOneButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerTwoButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerThreeButton.setImage(#imageLiteral(resourceName: "ElipseSelected"), for: .normal)
}
I tried several solutions with no luck like:
questions.removeAll()
in cellForRowAt
and in ViewWillAppear
I also tried to setup the buttons to un-selected choice in:
override func prepareForReuse() {
super.prepareForReuse()
answerOneButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerTwoButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerThreeButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
}
I've already spent many hours trying to solve this problem. and I read Apple docs regarding dequeueReusableCell
and collected many info with no result...Please anyone help!
Upvotes: 0
Views: 126
Reputation: 3791
From the Apple documentation prepareForReuse()
...
For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView(_:cellForRowAt:) should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called.
The most important point in this extract...
The table view's delegate in tableView(_:cellForRowAt:)
should always reset all content when reusing a cell.
My suggestion is to reset the content in tableView(_:cellForRowAt:)
.
For example...
cell.answerOneButton.setImage(UIImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
cell.answerTwoButton.setImage(UIImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
cell.answerThreeButton.setImage(UIImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
Forgive me if this sounds condescending (not intended) but it will help to think of the model and the view as separate. The view doesn’t know the (selection) state of the model, and the model doesn’t manage the view. The table view controller is the communicator (controller) between the two.
So maybe the fifth cell is not changing the answer to the fifth question in your model, instead the view is simply changing its presentation on screen based on code instructions in the controller. As your table view loads and/or reloads, it might be worth checking model values using breakpoints and/or print()
to terminal.
Upvotes: 1
Reputation: 89
The problem is that even if prepareForReuse will work (strange that it didn't), that will reuse your first cell when you dont need it. The best solution that I always use is to save cells state in delegate and reset them in tableView(_:cellForRowAt:)
For example:
In your case you can add to Question class property choosedAnswer and enum with 3 cases
enum answer {
case one
case two
case three
}
var choosedAnswer: answer?
In your cell.setQuestion(question:) function update button's images. Something like that:
switch question.choosedAnswer {
case .one:
answerOneButton.setImage(#imageLiteral(resourceName: "ElipseSelected"), for: .normal)
answerTwoButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerThreeButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
case .two:
answerOneButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerTwoButton.setImage(#imageLiteral(resourceName: "ElipseSelected"), for: .normal)
answerThreeButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
case .three:
answerOneButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerTwoButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerThreeButton.setImage(#imageLiteral(resourceName: "ElipseSelected"), for: .normal)
default:
answerOneButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerTwoButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
answerThreeButton.setImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
}
And dont forget to update models in your [Question] array after buttons select
func didTapAnswerOne(cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: sender) else {return}
questions[indexPath.row] = questions[indexPath.row] == .one ? nil : .one
}
func didTapAnswerTwo(cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: sender) else {return}
questions[indexPath.row] = questions[indexPath.row] == .two ? nil : .two
}
func didTapAnswerThree(cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: sender) else {return}
questions[indexPath.row] = questions[indexPath.row] == .three ? nil : .three
}
In your cell delegate tap functions send self:
delegate?.didTapAnswerOne(cell: self)
Upvotes: 3
Reputation: 91
Your UIViewController
that has tableView instance should know all the state that has been changed.
Something like this. You can create object to achieve something similar
[Question 1] --> [State of Answer1, state of Answer2, state of Answer3]
[Question 2] --> [State of Answer1, state of Answer2, state of Answer3]
[Question 3] --> [State of Answer1, state of Answer2, state of Answer3]
so forth..
Question 1 -> (False, False, False)
If user clicks on answer 2 of Question 1 then:
Question 1 -> (False, True, False)
then in cellForItem
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
.
.
(question[index.row].isTapped[answer])?
cell.answerOneButtonsetImage(#imageLiteral(resourceName: "ElipseSelected"), for: .normal)
:cell.answerOneButtonsetImage(#imageLiteral(resourceName: "Elipse"), for: .normal)
.
}
Upvotes: 0