Как обнаружить двойное касание ячеек в UICollectionView

Я хочу реагировать на двойные нажатия на ячейки в UICollectionView и иметь действие двойного нажатия, отменяющее выбор ячейки.

Это то, что я пробовал:

UITapGestureRecognizer *tapRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
tapRecogniser.numberOfTapsRequired = 2;

 for (UITapGestureRecognizer *recogniser in [self.collectionView gestureRecognizers]) {
    [recogniser requireGestureRecognizerToFail:tapRecogniser];
}

[self.collectionView addGestureRecognizer:tapRecogniser];

То есть я пытаюсь заставить распознаватели жестов по умолчанию терпеть неудачу, если мой распознаватель жестов двойного касания сработает.

Похоже, это не работает, так как collectionView:didSelectItemAtIndexPath: моего делегата представления коллекции все еще вызывается после двойного нажатия


Примечание к документам UICollectionViewController от Apple

Apple документация вводит в заблуждение по этому поводу, утверждая, что распознаватель жестов по умолчанию является экземпляром подкласса UITapGestureRecognizer, поэтому его можно легко выбрать с помощью [recogniser isKindOfClass:[UITapGestureRecognizer class]]. К сожалению, это ошибка.


person Cris    schedule 09.10.2012    source источник
comment
Я не вижу никакого конфликта, когда добавляю распознаватель касаний в представление коллекции — он отлично реагирует на двойные нажатия (он также реагирует на распознаватель одиночного касания представления коллекции).   -  person rdelmar    schedule 09.10.2012
comment
CollectionView:didSelectItemAtIndexPath: обратный вызов протокола UICollectionViewDelegate не работает (в моем тестировании) с добавленным распознавателем касаний.   -  person Cris    schedule 09.10.2012
comment
@rdelmar: спасибо за тестирование. Это работает и для меня сейчас (не знаю, что происходило в моем первоначальном тесте). Как следствие, я существенно изменил вопрос.   -  person Cris    schedule 10.10.2012


Ответы (5)


Я не понимаю, зачем вам нужен requireToFail. Я использую двойные нажатия в UICollectionView, и это не мешает моим одиночным нажатиям (используемым для выбора).

Я использую следующее:

UITapGestureRecognizer *doubleTapFolderGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(processDoubleTap:)];
[doubleTapFolderGesture setNumberOfTapsRequired:2];
[doubleTapFolderGesture setNumberOfTouchesRequired:1];
[self.view addGestureRecognizer:doubleTapFolderGesture];

Тогда это:

- (void) processDoubleTap:(UITapGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateEnded)
    {
        CGPoint point = [sender locationInView:collectionView];
        NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:point];
        if (indexPath)
        {
            NSLog(@"Image was double tapped");
        }
        else 
        {
            DoSomeOtherStuffHereThatIsntRelated;
        }
    }
}

Кажется, работает нормально - двойное касание распознается, и я могу обрабатывать его по своему усмотрению (в данном случае я расширяю содержимое папки). Но одно касание приведет к выбору продажи по постукиванию, для которого я не написал никакого распознавания жестов.

ВАЖНОЕ ИЗМЕНЕНИЕ:

Я возвращаюсь к этому вопросу, потому что вижу, что мой первоначальный ответ может быть неправильным при определенных обстоятельствах, и есть очевидное исправление, которое работает.

Необходимо добавить следующую строку:

doubleTapFolderGesture.delaysTouchesBegan = YES;

что устраняет помехи при одиночном нажатии для выбора соты. Это обеспечивает гораздо более надежную установку.

person RegularExpression    schedule 07.03.2013
comment
Все это относится к коду в проекте, над которым я больше не работаю, поэтому я не могу вернуться. Но IIRC я ​​пытался предотвратить вызов collectionView:didSelectItemAtIndexPath: при распознавании двойных нажатий. Я хотел, чтобы 2 жеста были взаимоисключающими. Так получилось, что дизайн приложения был изменен, чтобы все это стало неактуальным, поэтому я так и не дошел до решения проблемы. - person Cris; 09.03.2013
comment
Я использую тот же код, и он отлично работает, за одним исключением. Если представление коллекции прокручивается, то locationInView не выравнивается с прокручиваемым местоположением. Есть ли способ исправить это? - person Moe Salih; 12.07.2013

Здесь есть куча хороших решений, но, к сожалению, они не работали надежно для меня (например, я не мог заставить двойное касание срабатывать последовательно, возможно, потому, что я также реализовал didSelectItemAtIndexPath).

Что сработало для меня, так это добавление распознавателя жестов (двойного) касания в представление коллекции вместо ячейки. В его селекторе действий я определял, какая ячейка была дважды нажата, и делал все, что мне нужно было делать. Надеюсь, это поможет кому-то:

- (void)viewDidLoad
{
    UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didDoubleTapCollectionView:)];
    doubleTapGesture.numberOfTapsRequired = 2;
    [self.collectionView addGestureRecognizer:doubleTapGesture];
}

- (void)didDoubleTapCollectionView:(UITapGestureRecognizer *)gesture {

    CGPoint pointInCollectionView = [gesture locationInView:self.collectionView];
    NSIndexPath *selectedIndexPath = [self.collectionView indexPathForItemAtPoint:pointInCollectionView];
    UICollectionViewCell *selectedCell = [self.collectionView cellForItemAtIndexPath:selectedIndexPath];

    // do something
}
person Edwin Iskandar    schedule 19.03.2014
comment
Это должен быть принятый ответ. Работает как шарм. - person DrMickeyLauer; 05.01.2016
comment
Да, это лучший способ сделать это. Только один экземпляр UITapGestureRecognizer для каждого UICollectionView, а не, возможно, много-много-много... - person Peter; 18.05.2019
comment
Сочетание ответа @RegularExpression (doubleTapGesture.delaysTouchesBegan = true) с этим является IMO одним из лучших решений. Вы устраняете помехи между простым нажатием и двойным нажатием, сохраняя при этом простой подход. - person Jeremy Cochoy; 30.05.2019

Мое решение состояло в том, чтобы не реализовывать collectionView:didSelectItemAtIndexPath, а реализовать два распознавателя жестов.

    self.doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(processDoubleTap:)];
    [_doubleTapGesture setNumberOfTapsRequired:2];
    [_doubleTapGesture setNumberOfTouchesRequired:1];   

    [self.view addGestureRecognizer:_doubleTapGesture];

    self.singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(processSingleTap:)];
    [_singleTapGesture setNumberOfTapsRequired:1];
    [_singleTapGesture setNumberOfTouchesRequired:1];
    [_singleTapGesture requireGestureRecognizerToFail:_doubleTapGesture];

    [self.view addGestureRecognizer:_singleTapGesture];

Таким образом, я могу обрабатывать одиночные и двойные нажатия. Единственное, что я вижу, это то, что ячейка выбирается при двойном нажатии, но если это вас беспокоит, вы можете обработать ее в своих двух селекторах.

person Paul Cezanne    schedule 03.08.2013
comment
Это путь, по которому я пошел, и он работал хорошо для меня. Не забудьте удалить collectionView:didSelectItemAtIndexPath: если он у вас есть. - person Peter Johnson; 05.02.2014

requireGestureRecognizerToFail:, вызываемый для распознавателей жестов по умолчанию, действительно работает (то есть их состояние переходит в UIGestureRecognizerStateFailed, если распознано двойное касание).

Но кажется, что обратный вызов collectionView:didSelectItemAtIndexPath: делегата UICollectionView не учитывает это, т.е. он по-прежнему вызывается, когда распознаватель жестов по умолчанию дает сбой.

Таким образом, ответ/обходной путь заключается в том, чтобы убедиться, что реализации делегата collectionView:shouldSelectItemAtIndexPath: и collectionView:shouldDeselectItemAtIndexPath: проверяют состояние (одного из?) распознавателей жестов по умолчанию, таким образом:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {

    UITapGestureRecognizer *defaultGestureRecogniser = [[self.collectionView gestureRecognizers] objectAtIndex:0];
    return defaultGestureRecogniser.state != UIGestureRecognizerStateFailed;
}
person Cris    schedule 10.10.2012
comment
Оказывается, это не совсем правильно. Распознаватель жестов по умолчанию всегда находится в состоянии сбоя при входе в методы делегата выбора, если только не используется долгое касание. Таким образом, быстрое нажатие одним нажатием не может быть использовано для выбора ячейки. - person Cris; 10.10.2012

Для читателей, которые ищут быстрый ответ, это смесь ответа @RegularExpression и @Edwin Iskandar.

В вашем контроллере, удерживая collectionView, добавьте следующие строки:


  private var doubleTapGesture: UITapGestureRecognizer!
  func setUpDoubleTap() {
    doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(didDoubleTapCollectionView))
    doubleTapGesture.numberOfTapsRequired = 2
    collectionView.addGestureRecognizer(doubleTapGesture)

    // This line delay the single touch message to the collection view.
    // Simple touch will be sent only when the double tap recogniser is sure
    // this is a simple tap.
    // You can remove it if you don't mind having both a simple tap and double
    // tap event.
    doubleTapGesture.delaysTouchesBegan = true  
  }

  @objc func didDoubleTapCollectionView() {
    let pointInCollectionView = doubleTapGesture.location(in: collectionView)
    if let selectedIndexPath = collectionView.indexPathForItem(at: pointInCollectionView) {
      let selectedCell = collectionView.cellForItem(at: selectedIndexPath)

      // Print double tapped cell's path
      print(selectedCell)
    }
  }
person Jeremy Cochoy    schedule 30.05.2019