How can I enable selecting multiple collection view cells at once with multitouch?
First let's understand exactly what the problem is. The collectionView has a bunch of UIGestureRecognisers attached to it (for pan, touch, zoom, etc). Each recogniser has the same state machine of possible->recognised-> changed-> ended/failed. Each recogniser has a clear start beginning and end. Once the tap gesture has started in one location it is not going to begin in another location. When a person 1) touches down point A 2) touches down point B 3) touches up point A 4) touches up point B that the gesture completely ignores point B because it is "focused" on point A.
The second problem is that if you touch at two points at the exact same time the method of tapGesture.location(in: view)
will give you the average of those two location.
However we solve this the first step is to disable the collectionView tapGesture - it is not doing what we want :
self.collectionView.allowsMultipleSelection = true
self.collectionView.allowsSelection = false;
Next we are going to add our own tap gestures to each cell individually. This is explicitly NOT recommend by apple ("You should always attach your gesture recognizers to the collection view itself—not to a specific cell or view."1) but it will work:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
...
cell.addGestureRecognizer(UITapGestureRecognizer.init(target: self, action: #selector(didTap(tapper:))))
...
return cell;
}
@objc func didTap(tapper:UIGestureRecognizer) {
if let cell = tapper.view as? UICollectionViewCell{
if let index = collectionView.indexPath(for: cell) {
if collectionView.indexPathsForSelectedItems?.contains(index) ?? false {
collectionView.deselectItem(at: index, animated: true)
cell.isSelected = false
}else{
collectionView.selectItem(at: index, animated: true, scrollPosition: [])
cell.isSelected = true
}
}
}
}
You could add multiple gesture recognizers for the number of touches you want to support:
collectionView.allowsMultipleSelection = true
// allowing up to 5 finger touches, increase if you want for more :)
for i in 2...5 {
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
gestureRecognizer.numberOfTouchesRequired = i
gestureRecognizer.delegate = self
collectionView.addGestureRecognizer(gestureRecognizer)
}
, and have the controller to look for the cells that were touched:
@objc private func handleTap(_ gestureRecognizer: UIGestureRecognizer) {
// perform the action only after the touch ended
guard gestureRecognizer.state == .ended else { return }
for i in 0..<gestureRecognizer.numberOfTouches {
let location = gestureRecognizer.location(ofTouch: i, in: collectionView)
// if we have a cell at that point, toggle the selection
if let indexPath = collectionView.indexPathForItem(at: location) {
if collectionView.indexPathsForSelectedItems?.contains(indexPath) == true {
collectionView.deselectItem(at: indexPath, animated: true)
} else {
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])
}
}
}
}