'Toggle select / deselect state of a UICollectionView Cell on tap - Swift

So first of all i've been stuck on this for a few days and spent a full day reading and trying many options on Stack Overflow already but non to my success

What i'm trying to accomplish sounds simple and going over the Apple documentation it seems to me it should work https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewDelegate_protocol/#//apple_ref/occ/intfm/UICollectionViewDelegate/collectionView:shouldHighlightItemAtIndexPath:

Basically what i'm trying to achieve is to toggle the selected state of a UICollectionView Cell on tap.

First tap - Send the cell into a selected state and change background colour to white.

Second tap - Send the cell into a deselected state and change background colour to clear

ViewController -

 func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as? CollectionViewCell {
        cell.cellImage.image = UIImage(named: images[indexPath.row])
        return cell
    } else {
        return CollectionViewCell()
    }
}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? CollectionViewCell {
        cell.toggleSelectedState()
    }
}

func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
    if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? CollectionViewCell {
        cell.toggleSelectedState()
    }
}

Cell -

    func toggleSelectedState() {
    if selected {
        print("Selected")
        backgroundColor = UIColor.whiteColor()
    } else {
        backgroundColor = UIColor.clearColor()
        print("Deselected")
    }
}

The problem i'm having is the didDeselectItemAtIndexPath is not being called when tapping on a cell thats already selected, Though if i tap another cell it will get called and selects the new cell...

I have tried checking for selected states in shouldSelectItemAtIndexPath & shouldDeselectItemAtIndexPath, i even tried writing a tapGesture to get around this and still no luck...

Is there something i'm missing? Or is there any known work arounds to this? Any help would be greatly appreciated!



Solution 1:[1]

Maybe you can create a UIButton() with the same bounds as the cell, and identify the select in the button. Then in the tap action of the button, you can do something to 'disselect' the cell 'selected'.

Solution 2:[2]

Use the shouldSelectItemAt and check the indexPathsForSelectedItems property of the collection view to determine if the cell should be selected or deselected.

func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
    if let indexPaths = collectionView.indexPathsForSelectedItems, indexPaths.contains(indexPath) {
        collectionView.deselectItem(at: indexPath, animated: true)
        return false
    }

    return true
}

Solution 3:[3]

You may set the UICollectionView's allowsMultipleSelection property to YES(true), then the collection view will not deselect the previous item.

Solution 4:[4]

I made this work for a collection view that allows multiple cells to be selected and deselected by using a tap gesture recognizer.

Cell:

class ToggleCollectionViewCell: UICollectionViewCell {
    var didChangeSelection: (Bool) -> Void = { _ in }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        let tap = UITapGestureRecognizer(target: self, action: #selector(didTap))
        addGestureRecognizer(tap)
    }

    @objc private func didTap() {
        isSelected.toggle()
        didChangeSelection(isSelected)
    }
}

Controller:

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "toggleCell", for: indexPath)

    if let toggleCell = cell as? ToggleCollectionViewCell {
        // do your cell setup...
        lineCell.didChangeSelection = { isSelected in
            guard let self = self else { return }
            // ... handle selection/deselection
        }
    }

    return cell
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Måns Severin
Solution 3 ???
Solution 4 mhoeller