Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deselect UICollectionView cell on second tap

I have both tableViews and collectionViews in my project app. In both tables and collections, I want a row/cell to be selected when first tapped, and deselected when tapped second time.

With the tableView, I found a fairly straightforward and simple solution here, which works great.

CollectionView, on the other hand, turned out to be a whole different type of beast. Unlike tableView, there is no willSelectItemAt delegate method, so there is no way to check if item was in selected state before the second tap is registered. Implementing shouldDeselectItemAt and didDeselectItemAt gives no result - these methods never get called on a cell that's already selected when tapped.

The only plausible solution suggests creating a UIButton for each collectionView cell, but should it really be that complicated?

like image 494
Denys Triasunov Avatar asked Dec 13 '17 13:12

Denys Triasunov


3 Answers

Try using the "shouldSelectItem" UIColllectionViewDelegate method.

func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
    let item = collectionView.cellForItem(at: indexPath)
    if item?.isSelected ?? false {
        collectionView.deselectItem(at: indexPath, animated: true)
    } else {
        collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])
        return true
    }

    return false
}
like image 88
pkorosec Avatar answered Oct 10 '22 16:10

pkorosec


A shorter version of @pkorosec answer, with exact the same effect, is the following:

override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
    if collectionView.cellForItem(at: indexPath)?.isSelected ?? false {
        collectionView.deselectItem(at: indexPath, animated: true)
        return false
    }
    return true
}

An alternative, suggested by @Manav, is:

override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
    if collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false {
        collectionView.deselectItem(at: indexPath, animated: true)
        return false
    }
    return true
}
like image 38
Federico Zanetello Avatar answered Oct 10 '22 15:10

Federico Zanetello


Another option, that I personally think is way cleaner, is to allow multiple selection on collection view and then manually deselect the currently selected item before next selection.

First step: allow multiple selection

override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.allowsMultipleSelection = true
}

Second step: manually deselect previously selected item

func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
    collectionView.indexPathsForSelectedItems?.forEach { ip in
        collectionView.deselectItem(at: ip, animated: true)
    }
    return true
}
like image 38
Crt Gregoric Avatar answered Oct 10 '22 15:10

Crt Gregoric