I am writing an iOS card game. I display the player's cards in a collection view. The player can select one or more cards by tapping on them, and then press a deal button to deal the selected cards.
I want to allow the user to use multiple fingers to select multiple cards at once. For example, if the user wants to select 2 cards, he just needs to tap the two cards at the same time, with two fingers, and they will both be selected. It seems like that by default, UICollectionView
does not allow this. When I tap with 2 fingers, only one of the cards will be selected, even though the isMultipleTouchEnabled
property in UIView
is already set to true.
Note that I am not asking about how to allow a user to select multiple items in a collection view. I can and did already do that with allowsMultipleSelection = true
. What I am asking is how to allow the user to select 2 cells with 2 fingers (or n cells with n fingers).
I found this question, but that seems to be about how to show a border around a cell when it is selected.
I also looked into the documentation of UICollectionView
but I found no property that controls this.
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: [])
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With