Lars van poucke
Lars van poucke

Reputation: 25

UIButton inside custom UITableViewCell causes button to be selected in multiple cells

The goal of the UITableView is to display names with a custom styled checkbox beside it. But somehow when I tap the checkbox, multiple checkboxes in the UITableView get selected.

I've googled and implemented multiple solutions from different questions but none of them seem to work.

Custom UITableViewCell

import UIKit

class NameTableViewCell: UITableViewCell {

    var name = UILabel()

    let checkboxImage_unchecked = UIImage(named: "cellViewCheckbox_unchecked")
    let checkboxImage_checked = UIImage(named: "cellViewCheckbox_checked")

    let checkbox:UIButton

    weak var delegate:HandleCellInteractionDelegate?

    override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
        self.checkbox = UIButton(frame: CGRectMake(35, 16, self.checkboxImage_unchecked!.size.width/2, self.checkboxImage_unchecked!.size.height/2))
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.backgroundColor = UIColor.transparant()

        self.checkbox.setBackgroundImage(self.checkboxImage_unchecked, forState: .Normal)
        self.checkbox.setBackgroundImage(self.checkboxImage_checked, forState: .Selected)

        self.name = UILabel(frame: CGRectMake(97,18,200, 30))

        self.addSubview(self.name)
        self.addSubview(self.checkbox)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func setSelected(selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

    func checkboxPushed(sender:AnyObject){
        self.checkbox.selected = !self.checkbox.selected
        self.delegate?.didPressButton(self)            
    }

In the protocol I declare a method didPressButton as a callback for when the button is pressed (the delegate needs to implement it)

Protocol

import UIKit

protocol HandleCellInteractionDelegate:class {
    func didPressButton(cell:NameTableViewCell)
}

The ViewController

import UIKit

class NameViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, HandleCellInteractionDelegate{

    var nameView:NameView {
        get {
            return self.view as! NameView
        }
    }

    var names:[Name]?
    var firstLetters:[String]?

    let cellIdentifier = "nameCell"

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        //self.view.backgroundColor = UIColor.darkSalmon()
    }

    override func loadView() {
        let bounds = UIScreen.mainScreen().bounds
        self.view = NameView(frame: bounds)
        if(NSUserDefaults.standardUserDefaults().objectForKey("gender") !== nil){
            self.loadJSON()
            self.getFirstLetters();
            self.nameView.tableView.delegate = self;
            self.nameView.tableView.dataSource = self;
            self.nameView.tableView.registerClass(NameTableViewCell.classForCoder(), forCellReuseIdentifier: "nameCell")
            self.nameView.tableView.separatorStyle = .None
            self.nameView.tableView.rowHeight = 66.5            }
    }

    func loadJSON(){
        let path = NSBundle.mainBundle().URLForResource("names", withExtension: "json")

        let jsonData = NSData(contentsOfURL: path!)


        self.names = NamesFactory.createFromJSONData(jsonData!, gender: NSUserDefaults.standardUserDefaults().integerForKey("gender"))
    }

    func getFirstLetters(){
        var previous:String = ""

        for name in self.names! {
            let firstLetter = String(name.name.characters.prefix(1))
            if firstLetter != previous {
                previous = firstLetter
                self.firstLetters?.append(firstLetter)
                print(firstLetter)
            }

        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return (self.names!.count)
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        return nameCellAtIndexPath(indexPath)
    }

    func nameCellAtIndexPath(indexPath:NSIndexPath) -> NameTableViewCell {
        let cell = self.nameView.tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! NameTableViewCell
        self.setNameForCell(cell, indexPath: indexPath)
        cell.delegate = self;
        cell.checkbox.addTarget(cell, action: "checkboxPushed:", forControlEvents: .TouchUpInside)
        return cell
    }

    func setNameForCell(cell:NameTableViewCell, indexPath:NSIndexPath) {
        let item = self.names![indexPath.row] as Name
        cell.name.text = item.name
    }

    func didPressButton(cell: NameTableViewCell) {
        print(cell)
    }

}

What am I doing wrong & how can I fix it?

Upvotes: 0

Views: 809

Answers (2)

Prabhu.Somasundaram
Prabhu.Somasundaram

Reputation: 1390

You must have to save the check box state. Add one more attribute say checkboxStatus to your Model(Name).I hope you will have indexpath.row. After this line in nameCellAtIndexPath

cell.delegate = self;

set the tag like this

cell.tag = indexPath.row;

In the delegate method,

func didPressButton(cell: NameTableViewCell) {
    print(cell)
    let item = self.names![cell.tag] as Name
    item.checkboxstatus = cell.checkbox.selected
}

Just one crucial change,

func nameCellAtIndexPath(indexPath:NSIndexPath) -> NameTableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! NameTableViewCell
    self.setNameForCell(cell, indexPath: indexPath)
    cell.delegate = self;
    cell.tag = indexPath.row;
    cell.checkbox.addTarget(cell, action: "checkboxPushed:", forControlEvents: .TouchUpInside)
    let item = self.names![cell.tag] as Name
    cell.checkbox.selected = item.checkboxstatus
    return cell
}

Cheers!

Upvotes: 2

Jonas
Jonas

Reputation: 2259

This happens because the UITableViewCell are being reused.

You changed the cell when you press the checkbox, you need to keep track of that in your data source model.

In cellForRowAtIndexPath you have to add a condition to check if that checkbox was checked or not. Then you display the appropriate view accordingly.

Edit: In your example you need to check in nameCellAtIndexPath if the checkbox is checked or not and then display it correct.

Upvotes: 0

Related Questions