Gesto de pulsación larga en UICollectionViewCell

108

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

Lo que quiero hacer es: Mantenga presionada una celda ( tengo una cosa de calendario de github ), obtenga qué celda está presionada y luego haga cosas con ella. Necesito saber qué celda está presionada durante mucho tiempo. Perdón por esta amplia pregunta, pero no pude encontrar nada mejor ni en Google ni en SO.

Oscar Apeland
fuente

Respuestas:

220

C objetivo

En su myCollectionViewController.harchivo agregue el UIGestureRecognizerDelegateprotocolo

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

en su myCollectionViewController.marchivo:

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

Rápido 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
fuente
1
ya está en la respuesta: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];referencia aquí espero que todo esto merezca un premio de respuesta correcta: D
abbood
10
Para (al menos) ios7, debe agregar lpgr.delaysTouchesBegan = YES;para evitar que se active didHighlightItemAtIndexPathprimero.
DynamicDan
7
¿Por qué agregaste lpgr.delegate = self;? Funciona bien sin delegado, que tampoco ha proporcionado.
Yevhen Dubinin
3
@abbood la respuesta funciona, pero no puedo desplazarme hacia arriba y hacia abajo en la vista de colección (usando otro dedo) mientras el reconocedor de pulsación larga está activo. ¿Lo que da?
Pétur Ingi Egilsson
4
Personalmente, lo haría UIGestureRecognizerStateBegan, por lo que el gesto se usa cuando se reconoce, no cuando el usuario suelta el dedo.
Jeffrey Sun
28

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 te olvides del delegado UIGestureRecognizerDelegate

Guilherme de Freitas
fuente
3
Funcionó muy bien, solo una nota de que "handleLongPress:" debería cambiarse a #selector (YourViewController.handleLongPress (_ :))
Joseph Geraghty
16
Funciona bien, pero cambie UIGestureRecognizerState.Endeda UIGestureRecognizerState.Begansi desea que el código se active una vez que haya pasado la duración mínima, no solo cuando el usuario levante el dedo.
Crashalot
11

Utilice el delegado de UICollectionView para recibir un evento de pulsación prolongada

Debe implicar el método 3 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
}
liuyuning
fuente
Nota: Si devuelve falso para shouldShowMenuForItemAtIndexPath, didSelectItemAtIndexPath también se iniciará. Esto se volvió problemático para mí cuando quería dos acciones diferentes para pulsación larga frente a pulsación única.
ShannonS
son métodos obsoletos por ahora, puede usar - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (NSIndexPath *) indexPath point: (CGPoint) point;
Viktor Goltvyanitsa
8

Las respuestas aquí para agregar un reconocedor de gestos personalizado de pulsación larga son correctas, sin embargo, de acuerdo con la documentación aquí : la clase de UICollectionViewclase principal instala un default long-press gesture recognizerpara manejar las interacciones de desplazamiento, por lo que debe vincular su reconocedor de gestos de toque personalizado al reconocedor predeterminado asociado con su 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];
} 
tiguero
fuente
Veo lo que está diciendo, pero no es en blanco y negro, la documentación dice: 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.por lo que el reconocedor de pulsación larga predeterminado está hecho para desplazarse ... lo que implica que tiene que ir acompañado de un movimiento vertical ... el OP no pregunta sobre ese tipo de comportamiento ni está tratando de reemplazarlo
abbood
lo siento por sonar a la defensiva, pero he estado usando el código anterior con mi aplicación de iOS durante meses ... no puedo pensar en una sola vez que ocurrió un problema
técnico
@abbood eso está bien. No sé el componente de calendario de terceros que está usando el PO, pero creo que prevenir un error, incluso si crees que nunca puede suceder, no podría ser una mala idea ;-)
tiguero
Si debe esperar a que falle el reconocedor predeterminado, ¿no significa eso que habrá un retraso?
Crashalot
2
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

[cell addGestureRecognizer:longPress];

y agregue el método así.

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

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

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 algún lugar (la mejor opción está en su subclase si subclasificó UICollectionView)

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Por defecto reemplazar los métodos de inicialización initWithFrame:collectionViewLayout:y initWithCoder:ejemplo y agregue establecieron método para usted pulsación larga reconocedor gesto

@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 instancia el reconocedor de gestos de pulsación prolongada, 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 ese gesto falle antes de ser reconocido) y agregue el 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 fallan en ese gesto y permiten 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

Dmitry Fantastik
fuente
Este es el mismo baile que hice para mi clon de Snapchat. ahhh amor verdadero.
benjaminhallock
1

Rápido 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)")
    }
}

Además, no olvide implementar UIGestureRecognizerDelegate y llamar a setupLongGestureRecognizerOnCollection desde viewDidLoad o donde sea que necesite llamarlo.

Renexandro
fuente
0

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 movemos nuestro toque;
  • El reconocedor intercepta otras acciones táctiles, por lo que no podemos usar las devoluciones de llamada resaltadas de nuestro UICollectionView de manera adecuada.

Permítanme sugerir uno un poco de fuerza bruta, pero funcionando como se requiere sugerencia:

Declarando una descripción de devolución de llamada para un clic largo 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

Añadiendo dos métodos en nuestra clase de celda:

/**
 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 eventos táctiles predominantes aquí:

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

Luego, en algún lugar del controlador de nuestra vista de colección declarando el oyente de devolución de llamada:

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

Y finalmente en cellForItemAtIndexPath configurando la devolución de llamada para nuestras celdas:

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

Andrei K.
fuente