Жест долгого нажатия на UICollectionViewCell

108

Мне было интересно, как добавить распознаватель жестов долгого нажатия в (подкласс) UICollectionView. Я читал в документации, что он добавлен по умолчанию, но не могу понять, как.

Что я хочу сделать, так это: долгое нажатие на ячейку (у меня есть календарь из github ), получение, какая ячейка нажата, а затем что-то с ней делать. Мне нужно знать, какая ячейка нажата. Извините за этот широкий вопрос, но я не мог найти ничего лучше ни в Google, ни в SO

Оскар Апеланд
источник

Ответы:

220

Цель-C

В свой myCollectionViewController.hфайл добавьте UIGestureRecognizerDelegateпротокол

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

в вашем myCollectionViewController.mфайле:

- (void)viewDidLoad
{
    // attach long press gesture to collectionView
    UILongPressGestureRecognizer *lpgr 
       = [[UILongPressGestureRecognizer alloc]
                     initWithTarget:self action:@selector(handleLongPress:)];
    lpgr.delegate = self;
    lpgr.delaysTouchesBegan = YES;
    [self.collectionView addGestureRecognizer:lpgr];
}

-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
    if (gestureRecognizer.state != UIGestureRecognizerStateEnded) {
        return;
    }
    CGPoint p = [gestureRecognizer locationInView:self.collectionView];

    NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:p];
    if (indexPath == nil){
        NSLog(@"couldn't find index path");            
    } else {
        // get the cell at indexPath (the one you long pressed)
        UICollectionViewCell* cell =
        [self.collectionView cellForItemAtIndexPath:indexPath];
        // do stuff with the cell
    }
}

Swift

class Some {

    @objc func handleLongPress(gesture : UILongPressGestureRecognizer!) {
        if gesture.state != .Ended {
            return
        }
        let p = gesture.locationInView(self.collectionView)

        if let indexPath = self.collectionView.indexPathForItemAtPoint(p) {
            // get the cell at indexPath (the one you long pressed)
            let cell = self.collectionView.cellForItemAtIndexPath(indexPath)
            // do stuff with the cell
        } else {
            print("couldn't find index path")
        }
    }
}

let some = Some()
let lpgr = UILongPressGestureRecognizer(target: some, action: #selector(Some.handleLongPress))

Swift 4

class Some {

    @objc func handleLongPress(gesture : UILongPressGestureRecognizer!) {
        if gesture.state != .ended { 
            return 
        } 

        let p = gesture.location(in: self.collectionView) 

        if let indexPath = self.collectionView.indexPathForItem(at: p) { 
            // get the cell at indexPath (the one you long pressed) 
            let cell = self.collectionView.cellForItem(at: indexPath) 
            // do stuff with the cell 
        } else { 
            print("couldn't find index path") 
        }
    }
}

let some = Some()
let lpgr = UILongPressGestureRecognizer(target: some, action: #selector(Some.handleLongPress))
Abbood
источник
1
это уже в ответе: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];ссылка здесь надеюсь, что все это заслуживает награды за правильный ответ: D
abbood
10
Для (по крайней мере) ios7 вы должны добавить, lpgr.delaysTouchesBegan = YES;чтобы избежать didHighlightItemAtIndexPathсрабатывания в первую очередь.
DynamicDan 01
7
Зачем добавили lpgr.delegate = self;? Он отлично работает без делегата, которого вы также не предоставили.
Евгений Дубинин
3
@abbood ответ работает, но я не могу прокручивать вверх и вниз в обзоре коллекции (используя другой палец), пока активен распознаватель длительного нажатия. Что дает?
Петур Инги Эгильссон
4
Лично я бы так и поступил UIGestureRecognizerStateBegan, поэтому жест используется, когда он распознается, а не когда пользователь отпускает палец.
Джеффри Сан
28

Тот же код @abbood для Swift:

В viewDidLoad:

let lpgr : UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
lpgr.minimumPressDuration = 0.5
lpgr.delegate = self
lpgr.delaysTouchesBegan = true
self.collectionView?.addGestureRecognizer(lpgr)

И функция:

func handleLongPress(gestureRecognizer : UILongPressGestureRecognizer){

    if (gestureRecognizer.state != UIGestureRecognizerState.Ended){
        return
    }

    let p = gestureRecognizer.locationInView(self.collectionView)

    if let indexPath : NSIndexPath = (self.collectionView?.indexPathForItemAtPoint(p))!{
        //do whatever you need to do
    }

}

Не забывай делегата UIGestureRecognizerDelegate

Гильерме де Фрейтас
источник
3
Сработало отлично, просто обратите внимание, что "handleLongPress:" следует изменить на #selector (YourViewController.handleLongPress (_ :))
Джозеф Герати,
16
Работает хорошо, но измените UIGestureRecognizerState.Endedна, UIGestureRecognizerState.Beganесли вы хотите, чтобы код запускался по истечении минимальной продолжительности, а не только тогда, когда пользователь поднимает палец.
Crashalot
11

Используйте делегата UICollectionView, чтобы получить длинное событие нажатия

Вы должны использовать 3 метода ниже.

//UICollectionView menu delegate
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath{

   //Do something

   return YES;
}
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender{
    //do nothing
    return NO;
}

- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender{
    //do nothing
}
Лююнин
источник
Примечание. Если вы вернете false для shouldShowMenuForItemAtIndexPath, didSelectItemAtIndexPath также будет запущен. Это стало проблемой для меня, когда я хотел два разных действия: долгое нажатие и одно нажатие.
ShannonS
на данный момент это устаревшие методы, вы можете использовать - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (nonnull NSIndexPath *) indexPath point: (CGPoint) point;
Виктор Гольтвяница
8

Ответы здесь на добавление настраиваемого распознавателя жестов при длительном нажатии верны, однако согласно документации здесь : родительский класс UICollectionViewкласса устанавливает default long-press gesture recognizerдля обработки взаимодействий прокрутки, поэтому вы должны связать свой настраиваемый распознаватель жестов касания с распознавателем по умолчанию, связанным с представлением вашей коллекции.

Следующий код предотвратит взаимодействие вашего пользовательского распознавателя жестов с распознавателем по умолчанию:

UILongPressGestureRecognizer* longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];

longPressGesture.minimumPressDuration = .5; //seconds
longPressGesture.delegate = self;

// Make the default gesture recognizer wait until the custom one fails.
for (UIGestureRecognizer* aRecognizer in [self.collectionView gestureRecognizers]) {
   if ([aRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
      [aRecognizer requireGestureRecognizerToFail:longPressGesture];
} 
тигеро
источник
я понимаю, что вы говорите, но это не черно-белое, документация гласит: The parent class of UICollectionView class installs a default tap gesture recognizer and a default long-press gesture recognizer to handle scrolling interactions. You should never try to reconfigure these default gesture recognizers or replace them with your own versions.так что распознаватель длительного нажатия по умолчанию предназначен для прокрутки ... что означает, что он должен сопровождаться вертикальным перемещением ... OP не спрашивает о таком поведении и не пытается его заменить
abbood
извините за оборонительный настрой, но я уже несколько месяцев использую приведенный выше код в своем приложении для iOS ... не могу вспомнить ни единого
случая,
@abbood, все в порядке. Я не знаю сторонний компонент календаря, который использует
заказчик,
Если вам нужно дождаться отказа распознавателя по умолчанию, разве это не означает, что будет задержка?
Crashalot
2
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

[cell addGestureRecognizer:longPress];

и добавьте такой метод.

- (void)longPress:(UILongPressGestureRecognizer*)gesture
{
    if ( gesture.state == UIGestureRecognizerStateEnded ) {

        UICollectionViewCell *cellLongPressed = (UICollectionViewCell *) gesture.view;
    }
}
Satheeshwaran
источник
2

Чтобы иметь внешний распознаватель жестов и не конфликтовать с внутренними распознавателями жестов в UICollectionView, вам необходимо:

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

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

@property (strong, nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer;   

@end

Переопределите методы инициализации по умолчанию initWithFrame:collectionViewLayout:и initWithCoder:добавьте метод настройки для распознавания жестов при длительном нажатии

@implementation UICollectionViewSubclass

-(instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
{
    if (self = [super initWithFrame:frame collectionViewLayout:layout]) {
        [self setupLongPressGestureRecognizer];
    }
    return self;
}

-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder]) {
        [self setupLongPressGestureRecognizer];
    }
    return self;
}

@end

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

-(void)setupLongPressGestureRecognizer
{
    _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                                                                action:@selector(handleLongPressGesture:)];
    _longPressGestureRecognizer.delegate = self;

    for (UIGestureRecognizer *gestureRecognizer in self.collectionView.gestureRecognizers) {
        if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
            [gestureRecognizer requireGestureRecognizerToFail:_longPressGestureRecognizer];
        }
    }

    [self.collectionView addGestureRecognizer:_longPressGestureRecognizer];
}

Также не забудьте реализовать методы UIGestureRecognizerDelegate, которые не работают с этим жестом и позволяют одновременное распознавание (вам может потребоваться или не обязательно его реализовать, это зависит от других распознавателей жестов, которые у вас есть, или зависимостей от внутренних распознавателей жестов)

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if ([self.longPressGestureRecognizer isEqual:gestureRecognizer]) {
        return NO;
    }

    return NO;
}

учетные данные для этого идут во внутреннюю реализацию LXReorderableCollectionViewFlowLayout

Дмитрий Фантастик
источник
Это тот же танец, что я делал для своего клона Snapchat. аааа настоящая любовь.
benjaminhallock
2

Swift 5:

private func setupLongGestureRecognizerOnCollection() {
    let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(gestureRecognizer:)))
    longPressedGesture.minimumPressDuration = 0.5
    longPressedGesture.delegate = self
    longPressedGesture.delaysTouchesBegan = true
    collectionView?.addGestureRecognizer(longPressedGesture)
}

@objc func handleLongPress(gestureRecognizer: UILongPressGestureRecognizer) {
    if (gestureRecognizer.state != .began) {
        return
    }

    let p = gestureRecognizer.location(in: collectionView)

    if let indexPath = collectionView?.indexPathForItem(at: p) {
        print("Long press at item: \(indexPath.row)")
    }
}

Также не забудьте реализовать UIGestureRecognizerDelegate и вызвать setupLongGestureRecognizerOnCollection из viewDidLoad или там, где вам нужно его вызвать.

Ренександро
источник
0

Пожалуй, наиболее распространенным решением является использование UILongPressGestureRecognizer . Но сталкиваюсь с двумя неприятными неприятностями:

  • иногда этот распознаватель работает некорректно, когда мы перемещаем касание;
  • распознаватель перехватывает другие действия касания, поэтому мы не можем правильно использовать обратные вызовы выделения нашего UICollectionView.

Позвольте мне предложить немного грубой силы, но работающей так, как это требуется:

Объявление описания обратного вызова при длительном нажатии на нашу ячейку:

typealias OnLongClickListener = (view: OurCellView) -> Void

Расширение UICollectionViewCell с помощью переменных (мы можем назвать его, например, OurCellView):

/// To catch long click events.
private var longClickListener: OnLongClickListener?

/// To check if we are holding button pressed long enough.
var longClickTimer: NSTimer?

/// Time duration to trigger long click listener.
private let longClickTriggerDuration = 0.5

Добавляем два метода в наш класс ячейки:

/**
 Sets optional callback to notify about long click.

 - Parameter listener: A callback itself.
 */
func setOnLongClickListener(listener: OnLongClickListener) {
    self.longClickListener = listener
}

/**
 Getting here when long click timer finishs normally.
 */
@objc func longClickPerformed() {
    self.longClickListener?(view: self)
}

И основные события касания здесь:

/// Intercepts touch began action.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    longClickTimer = NSTimer.scheduledTimerWithTimeInterval(self.longClickTriggerDuration, target: self, selector: #selector(longClickPerformed), userInfo: nil, repeats: false)
    super.touchesBegan(touches, withEvent: event)
}

/// Intercepts touch ended action.
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    longClickTimer?.invalidate()
    super.touchesEnded(touches, withEvent: event)
}

/// Intercepts touch moved action.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    longClickTimer?.invalidate()
    super.touchesMoved(touches, withEvent: event)
}

/// Intercepts touch cancelled action.
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
    longClickTimer?.invalidate()
    super.touchesCancelled(touches, withEvent: event)
}

Затем где-то в контроллере нашего представления коллекции объявляет прослушиватель обратного вызова:

let longClickListener: OnLongClickListener = {view in
    print("Long click was performed!")
}

И, наконец, в настройке callback-функции cellForItemAtIndexPath для наших ячеек:

/// Data population.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
    let castedCell = cell as? OurCellView
    castedCell?.setOnLongClickListener(longClickListener)

    return cell
}

Теперь мы можем перехватывать длительные нажатия на наши ячейки.

Андрей К.
источник