Gesto de pressão longa em UICollectionViewCell


108

Eu queria saber como adicionar um reconhecedor de gestos de pressionar longamente a uma (subclasse de) UICollectionView. Eu li na documentação que ele é adicionado por padrão, mas não consigo descobrir como.

O que eu quero fazer é: Pressione e segure em uma célula ( eu tenho uma coisa de calendário do github ), pegue qual célula está tocada e faça outras coisas com ela. Preciso saber qual célula está comprimida. Desculpe por esta pergunta ampla, mas não consegui encontrar nada melhor no google ou no SO

Respostas:


220

Objective-C

Em seu myCollectionViewController.harquivo, adicione o UIGestureRecognizerDelegateprotocolo

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

em seu myCollectionViewController.marquivo:

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

1
já está na resposta: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];referência aqui espero que tudo isso mereça uma resposta correta Prêmio: D
— abbood

10
Para (pelo menos) ios7, você deve adicionar lpgr.delaysTouchesBegan = YES;para evitar que didHighlightItemAtIndexPathseja disparado primeiro.
— DynamicDan

7
Por que você adicionou lpgr.delegate = self;? Ele funciona bem sem delegado, que você também não forneceu.
— Yevhen Dubinin

3
@abbood, a resposta funciona, mas não consigo rolar para cima e para baixo na visualização da coleção (usando outro dedo) enquanto o reconhecedor de pressionamento longo está ativo. O que da?
— Pétur Ingi Egilsson

4
Pessoalmente, eu faria UIGestureRecognizerStateBegan, para que o gesto seja usado quando for reconhecido, não quando o usuário soltar o dedo.
— Jeffrey Sun,

28

O mesmo código @ código de abbood para Swift:

Em viewDidLoad:

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

E a função:

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ão se esqueça do delegado UIGestureRecognizerDelegate


3
Funcionou muito bem, apenas uma observação de que "handleLongPress:" deve ser alterado para #selector (YourViewController.handleLongPress (_ :))
— Joseph Geraghty

16
Funciona bem, mas mude UIGestureRecognizerState.Endedpara UIGestureRecognizerState.Beganse quiser que o código seja disparado depois que a duração mínima tiver passado, não apenas quando o usuário pegar o dedo.
— Crashalot

11

Use o delegado de UICollectionView para receber evento de imprensa longa

Você deve implantar o 3 método abaixo.

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

Observação: se você retornar false para shouldShowMenuForItemAtIndexPath, o didSelectItemAtIndexPath também será iniciado. Isso se tornou um problema para mim quando eu queria duas ações diferentes para pressionamento longo versus pressionamento único.
— ShannonS

são métodos obsoletos por enquanto, você pode usar - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (não nulo NSIndexPath *) indexPath ponto: (CGPoint) ponto;
— Viktor Goltvyanitsa

8

As respostas aqui para adicionar um reconhecedor de gestos de toque longo personalizado estão corretas, no entanto, de acordo com a documentação aqui : a classe pai da UICollectionViewclasse instala um default long-press gesture recognizerpara lidar com as interações de rolagem, portanto, você deve vincular seu reconhecedor de gestos de toque personalizado ao reconhecedor padrão associado à sua visualização de coleção.

O código a seguir evitará que o reconhecedor de gestos personalizado interfira com o padrão:

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

vejo o que você está dizendo, mas não é preto e branco, diz a documentação: 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.então o reconhecedor de pressionamento longo padrão é feito para rolagem .. o que implica que deve ser acompanhado por um movimento vertical .. o OP não está perguntando sobre esse tipo de comportamento e ele não está tentando substituí-lo
— abbood,

desculpe por soar na defensiva, mas estou usando o código acima com meu aplicativo iOS por meses. Não consigo me lembrar de uma única vez que uma falha aconteceu
— abbood

@abbood, tudo bem. Não sei o componente de calendário de terceiros que o pedido de compra está usando, mas acho que evitar uma falha, mesmo que você ache que isso nunca pode acontecer, não pode ser uma má ideia ;-)
— tiguero

Se você deve esperar que o reconhecedor padrão falhe, isso não significa que haverá um atraso?
— Crashalot

2
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

[cell addGestureRecognizer:longPress];

e adicione o método como este.

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

        UICollectionViewCell *cellLongPressed = (UICollectionViewCell *) gesture.view;
    }
}

2

Para ter um reconhecedor de gestos externo e não entrar em conflito com os reconhecedores de gestos internos no UICollectionView, você precisa:

Adicione seu reconhecedor de gestos, configure-o e capture uma referência para ele em algum lugar (a melhor opção está em sua subclasse se você criou uma subclasse de UICollectionView)

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Substituir métodos de inicialização padrão initWithFrame:collectionViewLayout:e initWithCoder:e adicionar configurar método para você pressão longa gesto reconhecedor

@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

Escreva seu método de configuração para instanciar o reconhecedor de gestos de toque longo, definir seu delegado, configurar dependências com o reconhecedor de gestos UICollectionView (para que seja o gesto principal e todos os outros gestos esperarão até que o gesto falhe antes de serem reconhecidos) e adicione o gesto à visualização

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

Também não se esqueça de implementar os métodos UIGestureRecognizerDelegate que falham nesse gesto e permitir o reconhecimento simultâneo (você pode ou não precisar implementá-lo, isso depende de outros reconhecedores de gesto que você tem ou dependências com reconhecedores de gesto internos)

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

    return NO;
}

credenciais para isso vão para a implementação interna de LXReorderableCollectionViewFlowLayout


Esta é a mesma dança que fiz para meu clone do Snapchat. ahhh amor verdadeiro.
— 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)")
    }
}

Além disso, não se esqueça de implementar UIGestureRecognizerDelegate e chamar setupLongGestureRecognizerOnCollection de viewDidLoad ou onde quer que você precise chamá-lo.


0

Talvez, usar UILongPressGestureRecognizer seja a solução mais difundida. Mas eu encontro com ele dois problemas irritantes:

  • às vezes, esse reconhecedor funciona de maneira incorreta quando movemos nosso toque;
  • reconhecedor intercepta outras ações de toque, portanto, não podemos usar callbacks de destaque de nosso UICollectionView de maneira adequada.

Deixe-me sugerir um pouco de força bruta, mas funcionando como é obrigatório, sugestão:

Declarando uma descrição de retorno de chamada para um clique longo em nosso celular:

typealias OnLongClickListener = (view: OurCellView) -> Void

Estendendo UICollectionViewCell com variáveis ​​(podemos chamá-lo de OurCellView, por exemplo):

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

Adicionando dois métodos em nossa classe de célula:

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

E substituindo eventos de toque aqui:

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

Então, em algum lugar no controlador de nossa visualização de coleção declarando listener de retorno de chamada:

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

E, finalmente, em cellForItemAtIndexPath definindo callback para nossas 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
}

Agora podemos interceptar longas ações de clique em nossas células.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.