Gesto di pressione lunga su UICollectionViewCell


108

Mi chiedevo come aggiungere un riconoscimento di gesti di pressione prolungata a una (sottoclasse di) UICollectionView. Ho letto nella documentazione che è aggiunto di default, ma non riesco a capire come.

Quello che voglio fare è: premere a lungo su una cella ( ho un oggetto del calendario da GitHub ), ottenere quale cella è stata toccata e quindi fare le cose con essa. Ho bisogno di sapere quale cella è premuta a lungo. Ci scusiamo per questa domanda ampia, ma non sono riuscito a trovare niente di meglio su Google o SO

Risposte:


220

Objective-C

Nel tuo myCollectionViewController.hfile aggiungi il UIGestureRecognizerDelegateprotocollo

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

nel tuo myCollectionViewController.mfile:

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

veloce

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
è già nella risposta: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];riferimento qui spero che tutto questo meriti una risposta corretta premio: D
abbood

10
Per (almeno) ios7 devi aggiungere lpgr.delaysTouchesBegan = YES;per evitare che didHighlightItemAtIndexPathvenga attivato prima.
DynamicDan

7
Perché hai aggiunto lpgr.delegate = self;? Funziona bene senza delegato, che non hai fornito.
Yevhen Dubinin

3
@abbood la risposta funziona, ma non riesco a scorrere su e giù nella collectionview (usando un altro dito) mentre è attivo il riconoscimento della pressione prolungata. Cosa succede?
Pétur Ingi Egilsson

4
Personalmente, lo farei UIGestureRecognizerStateBegan, quindi il gesto viene utilizzato quando viene riconosciuto, non quando l'utente rilascia il dito.
Jeffrey Sun

28

Lo stesso codice @ codice abbood per Swift:

In viewDidLoad:

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

E la funzione:

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
    }

}

Non dimenticare il delegato UIGestureRecognizerDelegate


3
Ha funzionato alla grande, solo una nota che "handleLongPress:" dovrebbe essere cambiato in #selector (YourViewController.handleLongPress (_ :))
Joseph Geraghty

16
Funziona bene, ma cambia UIGestureRecognizerState.Endedin UIGestureRecognizerState.Beganse desideri che il codice si attivi una volta trascorsa la durata minima, non solo quando l'utente solleva il dito.
Crashalot

11

Usa il delegato di UICollectionView per ricevere un evento di pressione prolungata

Devi impl 3 metodo di seguito.

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

Nota: se restituisci false per shouldShowMenuForItemAtIndexPath, verrà avviato anche didSelectItemAtIndexPath. Questo è diventato problematico per me quando volevo due diverse azioni per la pressione lunga rispetto alla stampa singola.
ShannonS

sono metodi deprecati per ora, è possibile utilizzare - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (nonnull NSIndexPath *) indexPath point: (CGPoint) point;
Viktor Goltvyanitsa

8

Le risposte qui per aggiungere un riconoscimento di gesti di pressione lunga personalizzato sono corrette, tuttavia, in base alla documentazione qui : la classe genitore della UICollectionViewclasse installa un default long-press gesture recognizerper gestire le interazioni di scorrimento, quindi è necessario collegare il riconoscimento di gesti di tocco personalizzato al riconoscimento predefinito associato alla visualizzazione della raccolta.

Il codice seguente eviterà che il tuo riconoscimento di gesti personalizzato interferisca con quello predefinito:

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

capisco cosa stai dicendo, ma non è in bianco e nero, la documentazione 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.quindi il riconoscimento predefinito della pressione prolungata è fatto per lo scorrimento .. il che implica che deve essere accompagnato da un movimento verticale .. l'OP non lo chiede su quel tipo di comportamento né sta cercando di sostituirlo
abbood

scusa per sembrare difensivo, ma ho usato il codice sopra con la mia app iOS per mesi .. non riesco a pensare a una sola volta che si è verificato un problema
tecnico

@abbood va bene. Non conosco il componente del calendario di terze parti che il PO sta utilizzando, ma penso che prevenire un problema tecnico anche se pensi che non possa mai accadere non potrebbe essere una cattiva idea ;-)
tiguero

Se devi aspettare che il riconoscimento predefinito fallisca, non significa che ci sarà un ritardo?
Crashalot

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

[cell addGestureRecognizer:longPress];

e aggiungi il metodo in questo modo.

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

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

2

Per avere un riconoscimento di gesti esterno e non entrare in conflitto con i riconoscitori di gesti interni su UICollectionView è necessario:

Aggiungi il tuo riconoscimento dei gesti, configuralo e acquisisci un riferimento da qualche parte (l'opzione migliore è nella tua sottoclasse se hai sottoclasse UICollectionView)

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Override di default metodi di inizializzazione initWithFrame:collectionViewLayout:e initWithCoder:ed aggiungere impostare il metodo per voi premere a lungo riconoscitore 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

Scrivi il tuo metodo di configurazione in modo da istanziare il riconoscimento dei gesti a pressione prolungata, impostare il suo delegato, impostare le dipendenze con il riconoscimento dei gesti UICollectionView (quindi è il gesto principale e tutti gli altri gesti aspetteranno fino a quando quel gesto fallisce prima di essere riconosciuto) e aggiungi il gesto alla 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];
}

Inoltre, non dimenticare di implementare metodi UIGestureRecognizerDelegate che falliscono quel gesto e consentono il riconoscimento simultaneo (potresti o non potresti aver bisogno di implementarlo, dipende da altri riconoscitori di gesti che hai o dipendenze con riconoscitori di gesti interni)

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

    return NO;
}

le credenziali per questo vanno all'implementazione interna di LXReorderableCollectionViewFlowLayout


Questa è la stessa danza che ho fatto per il mio clone di Snapchat. ahhh vero amore.
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)")
    }
}

Inoltre, non dimenticare di implementare UIGestureRecognizerDelegate e chiamare setupLongGestureRecognizerOnCollection da viewDidLoad o ovunque sia necessario chiamarlo.


0

Forse, l'utilizzo di UILongPressGestureRecognizer è la soluzione più diffusa. Ma incontro due fastidiosi guai:

  • a volte questo riconoscitore funziona in modo errato quando spostiamo il nostro tocco;
  • riconoscitore intercetta altre azioni di tocco, quindi non possiamo utilizzare i callback di evidenziazione del nostro UICollectionView in modo corretto.

Lasciatemi suggerirne uno un po 'bruteforce, ma lavorando come richiesto suggerimento:

Dichiarazione di una descrizione di richiamata per un clic lungo sulla nostra cella:

typealias OnLongClickListener = (view: OurCellView) -> Void

Estensione di UICollectionViewCell con variabili (possiamo chiamarlo OurCellView, ad esempio):

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

Aggiungendo due metodi nella nostra classe di celle:

/**
 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 sovrascrivere gli eventi di tocco qui:

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

Quindi da qualche parte nel controller della nostra vista della raccolta che dichiara il listener di callback:

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

E infine in cellForItemAtIndexPath impostando la richiamata per le nostre celle:

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

Ora possiamo intercettare azioni di clic prolungati sulle nostre celle.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.