Checkmarks are not removed from the table cell

I want to mark the items in the list with checkmarks. But some kind of error occurs because the checkmark is installed, but not removed.

Why is this happening?

This is a Model:

import UIKit

struct Food: Codable {

    var name: String

    var isSelected: Bool = false
}

This is a TableViewController:

import UIKit

class SelectFoods: UITableViewController {

    var foods = [Food]()

    override func viewDidLoad() {
        super.viewDidLoad()
        foods = loadRealOrDemoData() // Load data
    }

    // MARK: - Table view data source
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return foods.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "selectFood", for: indexPath)

        let food = foods[indexPath.row]
        cell.textLabel?.text = food.name

        // Assign checkmark from the selected item to the cell
        if food.isSelected {
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none
        }
        cell.selectionStyle = .none
        return cell
    }

    // MARK: - Select Food

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        // Create variable and check it.
        guard let cell = tableView.cellForRow(at: indexPath) else { return }

         //Clean cell format before select
        cell.accessoryType = .none

        // Get an item
        var item = foods[indexPath.row]

        // Set checkmark
        if item.isSelected == false {
            cell.accessoryType = .checkmark
            item.isSelected.toggle()
        // Remove checkmark
        } else {
            cell.accessoryType = .none
            item.isSelected.toggle()
        }
    }
}

Accepted Reply

I use this if condition inside of didSelectRowAt method

That seems to be the critical part of your code.

In your code, item is a local variable. Changing the property of it does not affect the instance property food.

Please try something like this:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        foods[indexPath.row].isSelected.toggle()
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }

UITableView manages cell selection internally. So, you may need to add some code to make your isSelected work with such internal selection management, but anyway, please try the code above and tell us what happens.

Replies

How do you deselect ? By clicking again the same cell, or clicking another one.

call

func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)

at the end of didSelectRowAt

Or try to implement

func tableView(UITableView, didDeselectRowAt: IndexPath)

to deselect a cell.

  1. "How do you deselect?"

I use this if condition inside of didSelectRowAt method

// Set a checkmark
        if item.isSelected == false {
            cell.accessoryType = .checkmark
            item.isSelected.toggle()//
// Remove a checkmark
        } else {
            cell.accessoryType = .none
            item.isSelected.toggle()//
        }

The fact is that in this condition only the first part is executed, and the second part (else) is never executed. And I cannot understand why?

  1. "call func reloadROws..."

I call tableView.reloadData() but it doesn't work.

I use this if condition inside of didSelectRowAt method

That seems to be the critical part of your code.

In your code, item is a local variable. Changing the property of it does not affect the instance property food.

Please try something like this:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        foods[indexPath.row].isSelected.toggle()
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }

UITableView manages cell selection internally. So, you may need to add some code to make your isSelected work with such internal selection management, but anyway, please try the code above and tell us what happens.

Thank you so much!

I made a following code:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        // Create variable and check it.
        guard let cell = tableView.cellForRow(at: indexPath) else { return }

         //Clean cell format before select food
        cell.accessoryType = .none

        // Set checkmark
        if foods[indexPath].isSelected == false {
            cell.accessoryType = .checkmark
            foods[indexPath].isSelected.toggle()
        // Remove checkmark
        } else {
            cell.accessoryType = .none
            foods[indexPath].isSelected.toggle()
        }

        LoadSaveData.saveToFile(foods: foods)
        tableView.reloadData()
    }

It works!

P.S.

But why is this happening? After all, next to it, in the cellForRowAt method, the same previous code is used:

        ...
         let food = foods[indexPath.row]
        ...
        // Assign checkmark
        if food.isSelected {
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none
        }
        ...

And it works.

Any ideas on this point, please.

  • Unless you call reloadRows(at:with:) or reloadData() (generally, reloadData() is too much just for updating a single cell), UITableView would not update the cell, meaning tableView(_:cellForRowAt:) would not be called.

Add a Comment

You wrote:

"Unless you call reloadRows(at:with:) or reloadData() (generally, reloadData() is too much just for updating a single cell), UITableView would not update the cell, meaning tableView(_:cellForRowAt:) would not be called."

I understand that. My question is about something else - why is the same operation performed differently?

Inside the cellForRowAt method, I can create a separate constant to hold the item from array and then check its isSelected property.

let food = foods[indexPath.row]
if food.isSelected {...

But inside the didSelectRowAt method, this code does not work and I need to retrieve an item from the original array on the fly to check its isSelected property.

if foods[indexPath].isSelected...

What could be the reason here? Any ideas please

  • Sorry, but I do not see what is happening just reading does not work. You should better start a new thread including your latest code, the expected result and the actual result.

Add a Comment

What "didn't work" was not the test, but the toggle:

        var item = foods[indexPath.row]
        if item.isSelected == false {       // <<-- This is correctly performed: you do test foods[indexPath.row]
            cell.accessoryType = .checkmark
            item.isSelected.toggle()       // <<-- This only changed the local var item, not the foods array
        // Remove checkmark
        } else {
            cell.accessoryType = .none
            item.isSelected.toggle()
        }

So, if foods array (the dataSource) is unchanged, you reload the same old data.

        let food = foods[indexPath.row]  // <<-- This was not changed at the end of deselect
        cell.textLabel?.text = food.name

        // Assign checkmark from the selected item to the cell
        if food.isSelected {
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none
        }

The point is that var item = foods[indexPath.row] does not creates a pointer on the foods item in array, it creates a new instance of Food with the content of the array item.

Уes, thanks for the help. Now I realized that the whole point is in this line of code:

item.isSelected.toggle()

Here, the value is not checked, here it is changed.

Let me ask one last question on this topic: if foods were class, would that make a difference? In fact , a class is a reference type and if we write such code

var item = foods[indexPath.row]

then it turns out that we are passing the link and the above line should perform the change.

item.isSelected.toggle()

Is that right?

if foods were class, would that make a difference?

YES.

Is that right?

Right.

if foods were class, would that make a difference?

Yes, because in this case you pass a reference. So when you modify

item.isSelected.toggle()       

this applies to the pointed reference, not to a copy of it as is the case with struct.

Thanks a lot guys! I really appreciate your help!