Jijidev
Jijidev

Reputation: 49

Accessing all the cells when a button in a cell is clicked

I have a UITableView with custom UITableViewCell. Each cell has a button in it and I would like to make all the buttons unclickable when one button is clicked. It is easy to access the cell that contains the clicked button but I need also to access all the other cells to make their button unclickable.

So when a button is clicked inside a cell, I need to loop through all the other cells... I found a way to walk up the hierarchy so that in the button action method, I go up one layer above to access the UITableView and from there I can access each cell and from each cell I can access their button and edit the isUserInteractionEnabled property. However this doesn't seem to be a good practice to make a cell access the TableView and then all the other cells.

class AnswerCell2: UITableViewCell {

let answerTextButton: UIButton = {
        let answerButton = UIButton()
        answerButton.setTitle("initial text", for: .normal)
        return answerButton
    }()


override init(style: UITableViewCell.CellStyle, reuseIdentifier: String!) {

       answerTextButton.addTarget(self, action: #selector(answerClicked), for: .touchUpInside)

}

@objc func answerClicked(sender: UIButton) {
// HERE I CAN ACCESS EASILY THE CELL WHERE THE BUTTON WAS CLICKED 
// AND THEN USE THE TABLEVIEW TO ACCESS OTHER CELLS AND THEIR BUTTONS 
// BUT THIS IS NOT A NICE WAY
    }

So I have a solution as I said but I'd like more a nice way to do it. All the solutions I find online are about how to access the cell where the button was clicked but this one is easy, what I need is to access the other cells and I would like to avoid doing so in the answerClicked() action method if possible so that the code stays clean.

Thanks.

Upvotes: 1

Views: 907

Answers (3)

flanker
flanker

Reputation: 4210

This could be achieved in a relatively light-weight approach thorugh either closures or delegate functions. Given tableView is already delegate-heavy I'd slightly prefer that approach.

create a simple protocol to allow the cell to inform the viewController

protocol CellStateRespondable {  //I'm sure there are better protocol names than this!
  var buttonsAreEnabled: Bool
  func cellButtonChangedSate(enabled: Bool)
}

Add a delegate variable to your AnswerCell2 class

weak var delegate: CellStateRespondable?

and adapt youranswerClicked(sender: UIButton) to call the delegate method

delegate?.cellButtonChangedState(enabled: sender.isEnabled)

make the ViewController (or other controller if you separate out tableView delegate functions) comply with the protocol through adding the variable and function, and add the function implementation

var buttonsAreEnabled = true

func cellButtonChangedSate(enabled: Bool) {
  buttonsAreEnabled = enabled
  if let visibleCells = tableView.visibleCells as? [AnswerCell2] {
    visibleCells.forEach {
      $0.button.isEnabled = enabled
    }
  }
}

and then adapt your cellForRowAt to set the delegate and the state of the button when creating the cell

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = //dequeue cell and configure as normal
  cell.delegate. = self
  cell.button.isEnabled = buttonsAreEnabled
  return cell
}

Upvotes: 0

PGDev
PGDev

Reputation: 24341

You can create a closure in custom UITableViewCell and call it whenever a button is pressed in the cell, i.e.

class TableViewCell: UITableViewCell {
    @IBOutlet weak var button: UIButton!
    var handler: (()->())?

    @IBAction func buttonClicked(_ sender: UIButton) {
        handler?()
    }
}

Now, set the closure in tableView(_:cellForRowAt:) method and use visibleCells property to enable/disable the buttons in other cells, i.e.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
    cell.textLabel?.text = arr[indexPath.row]
    cell.handler = {
        if let visibleCells = tableView.visibleCells as? [TableViewCell] {
            visibleCells.forEach({
                $0.button.isEnabled = ($0 === cell)
            })
        }

    }
    return cell
}

In case you want to persist the enable/disable button states while reloading, you need to store in your model.

Upvotes: 1

Scriptable
Scriptable

Reputation: 19758

I am not sure why you need to walk the hierarchy and specifically change the characteristics of the existing cells on screen.

Just store a value which represents the index of the selected button.

var indexPathForSelectedButton: IndexPath?

Anytime this changes, update the index in func answerClicked(sender: UIButton) and reload the table view.

in the cell for at method, check if the current indexPath is the same as the stored one and if so enable the button, disable if not

Upvotes: 0

Related Questions