Geste de pression long sur UICollectionViewCell

108

Je me demandais comment ajouter un outil de reconnaissance de geste de pression longue à une (sous-classe de) UICollectionView. J'ai lu dans la documentation qu'il est ajouté par défaut, mais je ne comprends pas comment.

Ce que je veux faire, c'est: appuyer longuement sur une cellule ( j'ai un calendrier de github ), obtenir quelle cellule est exploitée et faire des choses avec. J'ai besoin de savoir quelle cellule est pressée depuis longtemps. Désolé pour cette question générale, mais je n'ai rien trouvé de mieux sur google ou SO

Oscar Apeland
la source

Réponses:

220

Objectif c

Dans votre myCollectionViewController.hdossier, ajoutez le UIGestureRecognizerDelegateprotocole

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

dans votre myCollectionViewController.mdossier:

- (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
    }
}

Rapide

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
la source
1
c'est déjà dans la réponse: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];référence ici j'espère que tout cela mérite une réponse correcte Award: D
abbood
10
Pour (au moins) ios7, vous devez ajouter lpgr.delaysTouchesBegan = YES;pour éviter d' didHighlightItemAtIndexPathêtre déclenché en premier.
DynamicDan
7
Pourquoi avez-vous ajouté lpgr.delegate = self;? Cela fonctionne bien sans délégué, ce que vous n'avez pas non plus fourni.
Yevhen Dubinin
3
@abbood la réponse fonctionne, mais je ne peux pas faire défiler vers le haut et vers le bas dans la vue de la collection (en utilisant un autre doigt) tant que la reconnaissance de pression longue est active. Ce qui donne?
Pétur Ingi Egilsson
4
Personnellement, je le ferais UIGestureRecognizerStateBegan, donc le geste est utilisé lorsqu'il est reconnu, pas lorsque l'utilisateur relâche son doigt.
Jeffrey Sun
28

Le même code @ abbood's code pour Swift:

Dans viewDidLoad:

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

Et la fonction:

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
    }

}

N'oubliez pas le délégué UIGestureRecognizerDelegate

Guilherme de Freitas
la source
3
A bien fonctionné, juste une note que "handleLongPress:" devrait être changé en #selector (YourViewController.handleLongPress (_ :))
Joseph Geraghty
16
Fonctionne bien, mais changez UIGestureRecognizerState.Endedpour UIGestureRecognizerState.Begansi vous voulez que le code se déclenche une fois la durée minimale écoulée, pas seulement lorsque l'utilisateur prend son doigt.
Crashalot
11

Utilisez le délégué de UICollectionView pour recevoir un événement de presse longue

Vous devez impliquer 3 méthodes ci-dessous.

//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
}
liuyuning
la source
Remarque: Si vous retournez false pour shouldShowMenuForItemAtIndexPath, didSelectItemAtIndexPath sera également lancé. Cela est devenu problématique pour moi lorsque je voulais deux actions différentes pour la presse longue vs la presse unique.
ShannonS
ce sont des méthodes obsolètes pour le moment, vous pouvez utiliser - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (Nonnull NSIndexPath *) indexPath point: (CGPoint) point;
Viktor Goltvyanitsa
8

Les réponses ici pour ajouter un outil de reconnaissance de gestes longue pression personnalisé sont correctes, cependant, selon la documentation ici : la classe parente de la UICollectionViewclasse installe un default long-press gesture recognizerpour gérer les interactions de défilement, vous devez donc lier votre outil de reconnaissance de gestes personnalisé au module de reconnaissance par défaut associé à votre vue de collection.

Le code suivant évitera à votre outil de reconnaissance de gestes personnalisé d'interférer avec celui par défaut:

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];
} 
tiguero
la source
Je vois ce que vous dites, mais ce n'est pas en noir et blanc, la documentation dit: 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.donc le module de reconnaissance par défaut est fait pour le défilement .. ce qui implique qu'il doit être accompagné d'un mouvement vertical .. l'OP ne demande pas à propos de ce genre de comportement et il
n'essaye
désolé d'avoir l'air défensif, mais j'utilise le code ci - dessus avec mon application iOS depuis des mois ... je ne peux pas penser à une seule fois qu'un problème est survenu
abbood
@abbood c'est bien. Je ne connais pas le composant de calendrier tiers utilisé par le PO, mais je pense qu'empêcher un problème même si vous pensez que cela ne peut jamais arriver ne pourrait pas être une mauvaise idée ;-)
tiguero
Si vous devez attendre que le module de reconnaissance par défaut échoue, cela ne signifie-t-il pas qu'il y aura un retard?
Crashalot
2
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

[cell addGestureRecognizer:longPress];

et ajoutez la méthode comme celle-ci.

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

        UICollectionViewCell *cellLongPressed = (UICollectionViewCell *) gesture.view;
    }
}
Satheeshwaran
la source
2

Pour disposer d'un outil de reconnaissance de mouvement externe et ne pas entrer en conflit avec les outils de reconnaissance de mouvement internes sur UICollectionView, vous devez:

Ajoutez votre reconnaissance de gestes, configurez-le et capturez une référence quelque part (la meilleure option est sur votre sous-classe si vous avez sous-classé UICollectionView)

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Remplacer les méthodes d'initialisation par défaut initWithFrame:collectionViewLayout:et initWithCoder:et d' ajouter mis en place la méthode pour vous appuyez longuement sur le geste de reconnaissance

@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

Écrivez votre méthode de configuration de manière à ce qu'elle instancie le module de reconnaissance de geste de pression longue, définissez son délégué, configurez les dépendances avec le module de reconnaissance de geste UICollectionView (donc ce sera le geste principal et tous les autres gestes attendront que ce geste échoue avant d'être reconnu) et ajoutez un geste à la vue

-(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];
}

N'oubliez pas non plus d'implémenter les méthodes UIGestureRecognizerDelegate qui échouent à ce geste et autorisent la reconnaissance simultanée (vous pouvez ou non avoir besoin de l'implémenter, cela dépend des autres outils de reconnaissance de gestes que vous avez ou des dépendances avec les outils de reconnaissance de gestes internes)

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

    return NO;
}

les informations d'identification pour cela vont à l'implémentation interne de LXReorderableCollectionViewFlowLayout

Dmitry Fantastik
la source
C'est la même danse que j'ai faite pour mon clone Snapchat. ahhh le vrai amour.
benjaminhallock
1

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)")
    }
}

N'oubliez pas non plus d'implémenter UIGestureRecognizerDelegate et d'appeler setupLongGestureRecognizerOnCollection depuis viewDidLoad ou partout où vous devez l'appeler.

Renexandro
la source
0

Peut-être que l'utilisation de UILongPressGestureRecognizer est la solution la plus répandue. Mais j'y rencontre deux ennuis ennuyeux:

  • parfois, ce module de reconnaissance fonctionne de manière incorrecte lorsque nous déplaçons notre toucher;
  • Le module de reconnaissance intercepte les autres actions tactiles afin que nous ne puissions pas utiliser les rappels de surbrillance de notre UICollectionView de manière appropriée.

Laissez-moi en suggérer un un peu bruteforce, mais en travaillant comme il est nécessaire suggestion:

Déclarer une description de rappel pour un clic long sur notre cellule:

typealias OnLongClickListener = (view: OurCellView) -> Void

Extension de UICollectionViewCell avec des variables (nous pouvons le nommer OurCellView, par exemple):

/// 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

Ajout de deux méthodes dans notre classe de cellules:

/**
 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)
}

Et écraser les événements tactiles ici:

/// 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)
}

Puis quelque part dans le contrôleur de notre vue de collection déclarant l'écouteur de rappel:

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

Et enfin dans le callback de paramétrage cellForItemAtIndexPath pour nos cellules:

/// 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
}

Nous pouvons désormais intercepter les actions de clic long sur nos cellules.

Andrei K.
la source