amirbt17
amirbt17

Reputation: 611

How to add a checkmark to cells in UITableView (using a custom UITableViewCell subclass and dequeueReusableCell)

I would like to add checkmarks to my Table View cells. I would think to just implement cell.accessoryType = UITableViewCellAccessoryCheckmark in my didSelectRowAt method, but I can't get this to work when using a custom UITableViewCell subclass.

Custom cell:

class TableCell: UITableViewCell {
    
    @IBOutlet weak var tableLabel: UILabel!
    
    var arrayOneToDisplay:ArrayOne?
    let arrayOne = ArrayOne()
    
    func displayTable(_ currentArrayOne:ArrayOne) {
        
        // Keep a reference to the cell
        arrayOneToDisplay = currentArrayOne
        
        // Get the Table Cells data
        var tableCell = arrayOne.tableCells
        
        // Set the instructionLabel
        tableLabel.text = currentArrayOne.tableCells![0]
        
    }

ViewController:

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        // Make sure the array contains at least 1 item
        guard arrayOne.count > 0 else {
            return 0
        }
        
        // Return the number of items for this array
        let currentArrayOne = arrayOne[currentArrayOneIndex!]
        
        if currentArrayOne.tableCells != nil {
            return currentArrayOne.tableCells!.count
        }
        else {
            return 0
        }

    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        // Get a cell
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell", for: indexPath) as! TableCell
        
        // Get the instruction that the tableView is asking about
        let tableLabel = cell.tableLabel
        
        if tableLabel != nil {
            
            let currentArrayOne = arrayOne[currentArrayOneIndex!]
            
            if currentArrayOne.tableCells != nil && indexPath.row < currentArrayOne.tableCells!.count {
                // Set the text for the label
                tableLabel!.text = currentArrayOne.tableCells![indexPath.row]
            }
        }
        
        // Return the cell
        return cell

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

    }

}

When I try to implement cell.accessoryType in the ViewController, Xcode doesn't recognize accessoryType. I believe it's because my methods are for UITableView and not UITableViewCell, so the added wrinkle of using a custom cell is causing some confusion for me. Any guidance is much appreciated!

Upvotes: 0

Views: 1032

Answers (3)

vadian
vadian

Reputation: 285200

Sorry, your approach is completely wrong. Never use a view as a data source type. Don't.

Create a model, this is a simple example with name and isSelected properties

struct Model {
    let name : String
    var isSelected = false
}

and in controller ViewController declare a data source array containing Model instances

var arrayOne = [Model(name: "Foo"), Model(name: "Bar"), Model(name: "Baz")]

In the extension in numberOfRows just return the number of items in the array (your guard expression makes no sense at all), in cellForRow set the checkmark depending on isSelected and in didSelect toggle the isSelected property and reload the row.

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return arrayOne.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        // Get a cell
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell", for: indexPath) as! TableCell
        
        // Get the item of the data source array
        let item = arrayOne[indexPath.row]
        
        // update the view
        cell.tableLabel = item.name
        cell.accessoryType = item.isSelected ? .checkmark : .none   
        
        // Return the cell
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // toggle isSelected of the row in the model
        arrayOne[indexPath.row].isSelected.toggle()
      
        // reload the row to update the view
        tableView.reloadRows(at: [indexPath], with: .none)   
    }

}

In the custom cell declare only the outlet, nothing else

class TableCell: UITableViewCell {
    
    @IBOutlet weak var tableLabel: UILabel!
        
}

The benefit of this approach is that model and view are always in sync.

Upvotes: 1

lpizzinidev
lpizzinidev

Reputation: 13284

You should set your accessoryType in the cellForRowAt method, try to rewrite it as this:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell", for: indexPath) as! TableCell

        cell.accessoryType = .checkmark
        
        guard let arrayOneIndex = currentArrayOneIndex, arrayOneIndex < arrayOne.count else { return cell }

        let currentArrayOne = arrayOne[arrayOneIndex]

        guard let tableCells = currentArrayOne.tableCells, indexPath.row < tableCells.count else { return cell }

        cell.tableLabel.text = tableCells[indexPath.row]
        
        return cell
    }

Upvotes: 0

Ptit Xav
Ptit Xav

Reputation: 3219

Not sure to understand what you try to do. The usual way for table view is not to save tableviewcell in array. The model-view-controller (MVC) way is to save the contents of the cells (text, images, checkedOrNotChecked) in an array of struct or object inside the viewcontroller (the array is the model). Then in CellForRow, set the content of the cell with the array[indexPath.row] element and if checkedOrNotCheckindicator set cell.accessoryType to checkmark (the cell is the view). In didSelectRow , you just set the checkOrNotCheck indicator of indexPath.row element and then reload the table view or just reload the cell at indexPath. Thé ViewController is the controller in MVC

Upvotes: 0

Related Questions