Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I enable selecting multiple collection view cells at once with multitouch?

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.

like image 603
Sweeper Avatar asked Jan 26 '19 14:01

Sweeper


2 Answers

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
      }
    }
  }
}
like image 176
Jon Rose Avatar answered Oct 17 '22 23:10

Jon Rose


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: [])
            }
        }
    }
}
like image 21
Cristik Avatar answered Oct 17 '22 22:10

Cristik