Gesto de pulsación larga en UICollectionViewCell

Me preguntaba cómo agregar un reconocedor de gestos de pulsación larga a una (subclase de) UICollectionView. Leí en la documentación que se agrega de manera predeterminada, pero no puedo entender cómo.

Lo que quiero hacer es: presionar largamente en una celda ( tengo una cosa del calendario de Github ), obtener la celda que se toca y luego hacer cosas con ella. Necesito saber qué celda tiene pulsación larga. Lo siento por esta gran pregunta, pero no pude encontrar nada mejor ni en google ni en SO

C objective

En su archivo myCollectionViewController.h , agregue el protocolo UIGestureRecognizerDelegate

 @interface myCollectionViewController : UICollectionViewController 

en su archivo 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 } } 

Rápido

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

El mismo código de @ abbood para Swift:

En viewDidLoad:

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

Y la función:

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

No olvides al delegado UIGestureRecognizerDelegate

Use el delegado de UICollectionView receive long press event

Debes implementar 3 métodos a continuación.

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

Las respuestas aquí para agregar un reconocedor de gestos longpress personalizado son correctas, pero según la documentación aquí : la clase padre de la clase UICollectionView instala un default long-press gesture recognizer para manejar las interacciones de desplazamiento, por lo que debe vincular su reconocedor de gestos UICollectionView personalizado con el reconocedor predeterminado asociado con tu vista de colección

El siguiente código evitará que su reconocedor de gestos personalizado interfiera con el predeterminado:

 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]; } 
 UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; [cell addGestureRecognizer:longPress]; 

y agrega el método de esta manera.

 - (void)longPress:(UILongPressGestureRecognizer*)gesture { if ( gesture.state == UIGestureRecognizerStateEnded ) { UICollectionViewCell *cellLongPressed = (UICollectionViewCell *) gesture.view; } } 

Para tener un reconocedor de gestos externo y no entrar en conflicto con los reconocedores de gestos internos en UICollectionView, debe:

Agregue su reconocedor de gestos, configúrelo y capture una referencia para él en alguna parte (la mejor opción está en su subclase si ha subclase UICollectionView)

 @interface UICollectionViewSubclass : UICollectionView  @property (strong, nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer; @end 

Reemplazar los métodos de inicialización predeterminados initWithFrame:collectionViewLayout: e initWithCoder: y agregar el método de configuración para que mantenga pulsado el reconocedor de gestos

 @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 

Escriba su método de configuración para que ejemplifique el reconocedor de gestos de pulsación larga, configure su delegado, configure las dependencias con el reconocedor de gestos UICollectionView (para que sea el gesto principal y todos los demás gestos esperarán hasta que el gesto falle antes de ser reconocido) y agregue un gesto a la vista

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

Además, no olvide implementar los métodos UIGestureRecognizerDelegate que fallen ese gesto y permitan el reconocimiento simultáneo (puede o no necesitar implementarlo, depende de otros reconocedores de gestos que tenga o dependencias con reconocedores de gestos internos)

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

credenciales para eso va a la implementación interna de LXReorderableCollectionViewFlowLayout

Quizás, usar UILongPressGestureRecognizer es la solución más extendida. Pero me encuentro con dos problemas molestos:

  • a veces este reconocedor funciona de manera incorrecta cuando estamos moviendo nuestro toque;
  • el reconocedor intercepta otras acciones táctiles, por lo que no podemos usar devoluciones de llamada de nuestro UICollectionView de forma adecuada.

Permítanme sugerirle un poco de fuerza bruta, pero trabajando como se requiere sugerencia:

Declarando una descripción de callback por mucho tiempo, haga clic en nuestra celda:

typealias OnLongClickListener = (view: OurCellView) -> Void

Extendiendo UICollectionViewCell con variables (podemos llamarlo OurCellView, por ejemplo):

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

Agregar dos métodos en nuestra clase celular:

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

Y reemplazando eventos táctiles aquí:

 /// Intercepts touch began action. override func touchesBegan(touches: Set, 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, withEvent event: UIEvent?) { longClickTimer?.invalidate() super.touchesEnded(touches, withEvent: event) } /// Intercepts touch moved action. override func touchesMoved(touches: Set, withEvent event: UIEvent?) { longClickTimer?.invalidate() super.touchesMoved(touches, withEvent: event) } /// Intercepts touch cancelled action. override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { longClickTimer?.invalidate() super.touchesCancelled(touches, withEvent: event) } 

Luego, en algún lugar del controlador de nuestra vista de colección, se declara al oyente de callback:

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

Y finalmente en cellForItemAtIndexPath configurando la callback para nuestras células:

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

Ahora podemos interceptar acciones de clic largo en nuestras celdas.