UIAction calling multiple times when tapped on UIButton in CollectionViewCell

391 Views Asked by At

When I tap on button it will trigger multiple times.

CollectionViewCell file code

class PhotoCell: UICollectionViewCell {
    @IBOutlet weak var deleteButton: UIButton!
}

ViewController - cellforItemAt method implementation

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as? PhotoCell else { return UICollectionViewCell() }

        let action = UIAction { _ in
            print("Delete button tapped!!!", indexPath.row)
        }

        cell.deleteButton.addAction(action, for: .touchUpInside)

        return cell
    }

If I configure UIButton addTarget then it work fine but I am not sure why it's not working with addAction.

2

There are 2 best solutions below

0
On BEST ANSWER

One possible solution is to override prepareForReuse() in PhotoCell, as Johann mentioned.

preparaForReuse() is use to Performs any clean up necessary to prepare the view for use again.

We can remove all events from the button there!

class PhotoCell: UICollectionViewCell {
    @IBOutlet weak var deleteButton: UIButton!

     override func prepareForReuse() {
        super.prepareForReuse()
        self.deleteButton.removeTarget(nil, action: nil, for: .allEvents)
    }

}

Add action in func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) like normally we do.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as? PhotoCell else { return UICollectionViewCell() }
        let action = UIAction { _ in
            print("Delete button tapped!!!", indexPath.row)
        }
        cell.deleteButton.addAction(action, for: .touchUpInside)
        return cell
    }
2
On

The actions get triggered multiple times because some instances of PhotoCell are reused by the UICollectionView. This means that the deleteButton of the PhotoCell returned by dequeueReusableCell() may already have actions added to it and will eventually possess multiple actions for the same events.

One possible solution is to override prepareForReuse() in PhotoCell and remove the actions there.

class PhotoCell: UICollectionViewCell {
    @IBOutlet weak var deleteButton: UIButton!
    var actions: [(UIAction, UIControl.Event)] = []

    func addAction(_ action: UIAction, for event: UIControl.Event) {
        self.actions.append((action, event))
        self.deleteButton.addAction(action, for: event)
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        for tuple in self.actions {
            self.deleteButton.removeAction(tuple.0, for: tuple.1)
        }
        self.actions = []
    }
}

When using the above code addAction needs to be called on cell instead of cell.deleteButton in the function func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath):

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as? PhotoCell else { return UICollectionViewCell() }

    let action = UIAction { _ in
        print("Delete button tapped!!!", indexPath.row)
    }
    cell.addAction(action, for: .touchUpInside)

    return cell
}