Lange Druckgeste auf UICollectionViewCell

108

Ich habe mich gefragt, wie ich einer (Unterklasse von) UICollectionView einen langen Gestenerkenner hinzufügen kann. Ich habe in der Dokumentation gelesen, dass es standardmäßig hinzugefügt wird, aber ich kann nicht herausfinden, wie.

Was ich tun möchte, ist: Lang auf eine Zelle drücken ( ich habe ein Kalender-Ding von Github ), herausfinden , welche Zelle getippt ist, und dann Sachen damit machen. Ich muss wissen, welche Zelle lang gedrückt ist. Entschuldigung für diese allgemeine Frage, aber ich konnte weder bei Google noch bei SO etwas Besseres finden

Oscar Apeland
quelle

Antworten:

220

Ziel c

Fügen Sie in Ihrer myCollectionViewController.hDatei das UIGestureRecognizerDelegateProtokoll hinzu

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

in Ihrer myCollectionViewController.mDatei:

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

Schnell

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))
Abbood
quelle
1
es ist bereits in der Antwort: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];Referenz hier hoffe, all dies
verdient
10
Für (mindestens) ios7 müssen Sie hinzufügen lpgr.delaysTouchesBegan = YES;, um zu vermeiden, didHighlightItemAtIndexPathdass zuerst ausgelöst wird.
DynamicDan
7
Warum hast du hinzugefügt lpgr.delegate = self;? Es funktioniert gut ohne Delegierten, die Sie auch nicht angegeben haben.
Yevhen Dubinin
3
@abbood die Antwort funktioniert, aber ich kann nicht in der Sammlungsansicht nach oben und unten scrollen (mit einem anderen Finger), während der lange Druckerkenner aktiv ist. Was gibt?
Pétur Ingi Egilsson
4
Persönlich würde ich das tun UIGestureRecognizerStateBegan, also wird die Geste verwendet, wenn sie erkannt wird, nicht wenn der Benutzer seinen Finger loslässt.
Jeffrey Sun
28

Der gleiche Code @ abboods Code für 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)

Und die Funktion:

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
    }

}

Vergessen Sie den Delegierten nicht UIGestureRecognizerDelegate

Guilherme de Freitas
quelle
3
Hat super funktioniert, nur ein Hinweis, dass "handleLongPress:" in #selector (YourViewController.handleLongPress (_ :)) geändert werden sollte
Joseph Geraghty
16
Funktioniert gut, aber wechseln Sie UIGestureRecognizerState.Endedzu, UIGestureRecognizerState.Beganwenn der Code nach Ablauf der Mindestdauer ausgelöst werden soll, nicht nur, wenn der Benutzer seinen Finger aufnimmt.
Crashalot
11

Verwenden Sie den Delegaten von UICollectionView, um eine lange Presseveranstaltung zu erhalten

Sie müssen 3 Methode unten implizieren.

//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
quelle
Hinweis: Wenn Sie für shouldShowMenuForItemAtIndexPath false zurückgeben, wird auch didSelectItemAtIndexPath gestartet. Dies wurde für mich problematisch, als ich zwei verschiedene Aktionen für lange Presse gegen einzelne Presse wollte.
ShannonS
Derzeit handelt es sich um veraltete Methoden. Sie können Folgendes verwenden: (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (nicht null NSIndexPath *) indexPath point: (CGPoint) point;
Viktor Goltvyanitsa
8

Die Antworten hier zum Hinzufügen eines benutzerdefinierten Longpress-Gestenerkenners sind jedoch gemäß der Dokumentation hier korrekt : Die übergeordnete Klasse der UICollectionViewKlasse installiert a default long-press gesture recognizer, um Bildlaufinteraktionen zu verarbeiten. Sie müssen Ihren benutzerdefinierten Tippgestenerkenner also mit dem Standarderkenner verknüpfen, der Ihrer Sammlungsansicht zugeordnet ist.

Mit dem folgenden Code wird verhindert, dass Ihre benutzerdefinierte Gestenerkennung die Standarderkennung beeinträchtigt:

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
quelle
Ich verstehe, was Sie sagen, aber es ist nicht schwarzweiß, heißt 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.es in der Dokumentation: Der Standard-Langdruckerkenner ist für das Scrollen vorgesehen. Dies bedeutet, dass er mit einer vertikalen Bewegung einhergehen muss. Das OP fragt nicht über diese Art von Verhalten noch versucht er, es zu ersetzen
Abbood
Es tut mir leid, dass ich defensiv klinge, aber ich verwende den obigen Code seit Monaten mit meiner iOS-App. Ich kann mir kein einziges Mal
vorstellen,
@abbood das ist in Ordnung. Ich weiß nicht, welche Kalenderkomponente von Drittanbietern die Bestellung verwendet, aber ich denke, dass es keine schlechte Idee sein kann, eine
Panne zu
Wenn Sie warten müssen, bis die Standarderkennung fehlschlägt, bedeutet dies nicht, dass es zu einer Verzögerung kommt?
Crashalot
2
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

[cell addGestureRecognizer:longPress];

und fügen Sie die Methode wie folgt hinzu.

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

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

Um einen externen Gestenerkenner zu haben und nicht mit internen Gestenerkennern in der UICollectionView in Konflikt zu stehen, müssen Sie:

Fügen Sie Ihren Gestenerkenner hinzu, richten Sie ihn ein und erfassen Sie irgendwo eine Referenz dafür (die beste Option ist in Ihrer Unterklasse, wenn Sie UICollectionView unterklassifiziert haben).

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Außer Kraft setzen Standardinitialisierung Methoden initWithFrame:collectionViewLayout:und initWithCoder:und fügen Sie lange drücken Gestenerkenner einrichten Methode

@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

Schreiben Sie Ihre Setup-Methode so, dass sie die Gestenerkennung mit langem Druck instanziiert, ihren Delegaten festlegt, Abhängigkeiten mit der UICollectionView-Gestenerkennung einrichtet (es handelt sich also um die Hauptgeste, und alle anderen Gesten warten, bis diese Geste fehlschlägt, bevor sie erkannt wird), und fügen Sie der Ansicht eine Geste hinzu

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

Vergessen Sie auch nicht, UIGestureRecognizerDelegate-Methoden zu implementieren, die diese Geste nicht bestehen und die gleichzeitige Erkennung ermöglichen (möglicherweise müssen Sie sie implementieren oder nicht, dies hängt von anderen Gestenerkennern oder Abhängigkeiten mit internen Gestenerkennern ab).

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

    return NO;
}

Die Anmeldeinformationen dafür gehen an die interne Implementierung von LXReorderableCollectionViewFlowLayout

Dmitry Fantastik
quelle
Dies ist der gleiche Tanz, den ich für meinen Snapchat-Klon gemacht habe. ahhh wahre Liebe.
Benjamin
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)")
    }
}

Vergessen Sie auch nicht, UIGestureRecognizerDelegate zu implementieren und setupLongGestureRecognizerOnCollection von viewDidLoad oder von jedem Ort aus aufzurufen, an dem Sie es aufrufen müssen.

Renexandro
quelle
0

Möglicherweise ist die Verwendung von UILongPressGestureRecognizer die am weitesten verbreitete Lösung. Aber ich stoße damit auf zwei nervige Probleme:

  • Manchmal funktioniert dieser Erkenner falsch, wenn wir unsere Berührung bewegen.
  • Der Erkenner fängt andere Berührungsaktionen ab, sodass wir Hervorhebungsrückrufe unserer UICollectionView nicht ordnungsgemäß verwenden können.

Lassen Sie mich ein wenig Bruteforce vorschlagen, aber so arbeiten, wie es erforderlich ist:

Eine Rückrufbeschreibung für einen langen Klick auf unsere Zelle deklarieren:

typealias OnLongClickListener = (view: OurCellView) -> Void

Erweitern von UICollectionViewCell um Variablen (wir können es beispielsweise OurCellView nennen):

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

Hinzufügen von zwei Methoden in unserer Zellklasse:

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

Und übergeordnete Berührungsereignisse hier:

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

Dann irgendwo in der Steuerung unserer Sammlungsansicht, die den Rückruf-Listener deklariert:

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

Und schließlich in cellForItemAtIndexPath Rückruf für unsere Zellen festlegen :

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

Jetzt können wir lange Klickaktionen auf unseren Zellen abfangen.

Andrei K.
quelle