Długie naciśnięcie gestu na UICollectionViewCell

108

Zastanawiałem się, jak dodać aparat rozpoznawania gestów długiego naciśnięcia do (podklasy) UICollectionView. W dokumentacji przeczytałem, że jest dodawany domyślnie, ale nie wiem jak.

Chcę: Długie naciśnięcie komórki ( mam kalendarz z github ), sprawdzenie , która komórka jest dotknięta, a następnie robienie z nią rzeczy. Muszę wiedzieć, która komórka jest długo prasowana. Przepraszam za to szerokie pytanie, ale nie mogłem znaleźć nic lepszego ani w Google, ani w SO

Oscar Apeland
źródło

Odpowiedzi:

220

Cel C

W swoim myCollectionViewController.hpliku dodaj UIGestureRecognizerDelegateprotokół

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

w twoim myCollectionViewController.mpliku:

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

Szybki

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

Szybki 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
źródło
1
to już jest w odpowiedzi: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];odniesienie tutaj mam nadzieję , że wszystko to zasługuje na poprawną odpowiedź nagroda: D
abbood
10
W przypadku (przynajmniej) ios7 musisz dodać, lpgr.delaysTouchesBegan = YES;aby uniknąć wyzwolenia w didHighlightItemAtIndexPathpierwszej kolejności.
DynamicDan
7
Dlaczego dodałeś lpgr.delegate = self;? Działa dobrze bez delegata, którego również nie podałeś.
Yevhen Dubinin
3
@abbood odpowiedź działa, ale nie mogę przewijać w górę iw dół w widoku kolekcji (używając innego palca), gdy aparat rozpoznawania długiego naciśnięcia jest aktywny. Co daje?
Pétur Ingi Egilsson
4
Osobiście bym to zrobił UIGestureRecognizerStateBegan, więc gest jest używany, gdy zostanie rozpoznany, a nie gdy użytkownik zwolni palec.
Jeffrey Sun
28

Ten sam kod @ abbood dla Swift:

W widokuDidLoad:

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

I funkcja:

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
    }

}

Nie zapomnij o delegacie UIGestureRecognizerDelegate

Guilherme de Freitas
źródło
3
Działało świetnie, tylko uwaga, że ​​„handleLongPress:” należy zmienić na #selector (YourViewController.handleLongPress (_ :))
Joseph Geraghty
16
Działa dobrze, ale zmień UIGestureRecognizerState.Endedna, UIGestureRecognizerState.Beganjeśli chcesz, aby kod był uruchamiany po upływie minimalnego czasu trwania, a nie tylko wtedy, gdy użytkownik podniesie palec.
Crashalot
11

Użyj delegata UICollectionView otrzymania zdarzenia długiego naciśnięcia

Musisz zastosować metodę impl 3 poniżej.

//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
źródło
Uwaga: Jeśli zwrócisz false dla shouldShowMenuForItemAtIndexPath, również didSelectItemAtIndexPath zostanie uruchomiona. Stało się to dla mnie problematyczne, kiedy chciałem dwóch różnych akcji dla długiego naciśnięcia i pojedynczego naciśnięcia.
ShannonS
na razie jest to przestarzałe metody, możesz użyć - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (nonnull NSIndexPath *) indexPath point: (CGPoint) point;
Viktor Goltvyanitsa
8

Odpowiedzi tutaj dotyczące dodania niestandardowego aparatu rozpoznawania gestów długiego naciśnięcia są poprawne, jednak zgodnie z dokumentacją tutaj : klasa nadrzędna klasy UICollectionViewinstaluje a, default long-press gesture recognizeraby obsługiwać interakcje przewijania, więc musisz połączyć swój niestandardowy aparat rozpoznawania gestów dotknięcia z domyślnym urządzeniem rozpoznającym skojarzonym z widokiem kolekcji.

Poniższy kod pozwoli uniknąć kolizji Twojego niestandardowego aparatu rozpoznawania gestów z domyślnym:

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
źródło
Widzę, co mówisz, ale nie jest to czarno-białe, dokumentacja mówi: 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.więc domyślny aparat rozpoznający z długim naciśnięciem jest przeznaczony do przewijania ... co oznacza, że ​​musi mu towarzyszyć ruch w pionie .. OP nie pyta o tego rodzaju zachowaniu, ani nie próbuje go zastąpić
opata
przepraszam, że zabrzmiało to defensywnie, ale od miesięcy używam powyższego kodu w mojej aplikacji na iOS ... nie mogę sobie wyobrazić ani jednego razu, gdy zdarzyła się usterka
opat
@abbood, w porządku. Nie znam komponentu kalendarza innej firmy, z którego korzysta PO, ale myślę, że zapobieganie usterce, nawet jeśli myślisz, że to się nigdy nie zdarzy, nie może być złym pomysłem ;-)
tiguero
Jeśli musisz poczekać na awarię domyślnego aparatu rozpoznawania, czy to nie oznacza, że ​​wystąpi opóźnienie?
Crashalot
2
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

[cell addGestureRecognizer:longPress];

i dodaj taką metodę.

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

        UICollectionViewCell *cellLongPressed = (UICollectionViewCell *) gesture.view;
    }
}
Satheeshwaran
źródło
2

Aby mieć zewnętrzny aparat rozpoznawania gestów i nie powodować konfliktu z wewnętrznymi aparatami rozpoznawania gestów w UICollectionView, musisz:

Dodaj swój aparat do rozpoznawania gestów, skonfiguruj go i przechwyć gdzieś odniesienie do niego (najlepsza opcja znajduje się w Twojej podklasie, jeśli podklasa UICollectionView)

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Domyślnym nadpisanie metody inicjalizacji initWithFrame:collectionViewLayout:i initWithCoder:i dodaj skonfigurować metodę Ci długo prasowej gest rozpoznawania

@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

Napisz swoją metodę konfiguracji, aby tworzyła instancję rozpoznawania gestów długiego naciśnięcia, ustaw jej delegata, konfiguruj zależności za pomocą rozpoznawania gestów UICollectionView (tak, aby był to główny gest, a wszystkie inne gesty będą czekać, aż ten gest zakończy się niepowodzeniem przed rozpoznaniem) i dodaj gest do widoku

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

Nie zapomnij również zaimplementować metod UIGestureRecognizerDelegate, które zawiodą ten gest i zezwalają na jednoczesne rozpoznawanie (możesz lub nie musisz tego implementować, zależy to od innych posiadanych mechanizmów rozpoznawania gestów lub zależności z wewnętrznymi aparatami rozpoznawania gestów)

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

    return NO;
}

poświadczenia tego trafiają do wewnętrznej implementacji LXReorderableCollectionViewFlowLayout

Dmitry Fantastik
źródło
To ten sam taniec, który wykonałem dla mojego klona Snapchata. ahhh prawdziwa miłość.
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)")
    }
}

Nie zapomnij również zaimplementować UIGestureRecognizerDelegate i wywołać setupLongGestureRecognizerOnCollection z viewDidLoad lub gdziekolwiek chcesz to wywołać.

Renexandro
źródło
0

Być może użycie UILongPressGestureRecognizer jest najbardziej rozpowszechnionym rozwiązaniem. Ale napotykam z nim dwa irytujące problemy:

  • czasami ten moduł rozpoznający działa nieprawidłowo, gdy poruszamy dotykiem;
  • Rozpoznawanie przechwytuje inne akcje dotykowe, więc nie możemy używać wywołań zwrotnych podświetlenia naszego UICollectionView we właściwy sposób.

Pozwólcie, że zasugeruję trochę brutalną siłę, ale działającą zgodnie z wymaganiami:

Deklarowanie opisu oddzwonienia dla długiego kliknięcia na naszą komórkę:

typealias OnLongClickListener = (view: OurCellView) -> Void

Rozszerzanie UICollectionViewCell o zmienne (możemy nazwać go na przykład OurCellView):

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

Dodanie dwóch metod do naszej klasy komórek:

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

I nadrzędne zdarzenia dotykowe tutaj:

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

Następnie gdzieś w kontrolerze naszego widoku kolekcji deklaruje odbiornik wywołań zwrotnych:

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

I na koniec w cellForItemAtIndexPath ustawienie wywołania zwrotnego dla naszych komórek:

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

Teraz możemy przechwytywać długie kliknięcia na naszych komórkach.

Andrei K.
źródło