Gerakan tekan lama di UICollectionViewCell

108

Saya bertanya-tanya bagaimana cara menambahkan pengenal gerakan tekan lama ke (subclass) UICollectionView. Saya membaca di dokumentasi bahwa itu ditambahkan secara default, tetapi saya tidak tahu caranya.

Yang ingin saya lakukan adalah: Tekan lama pada sel ( saya memiliki kalender dari github ), dapatkan sel mana yang disadap dan kemudian lakukan hal-hal dengannya. Saya perlu tahu sel apa yang ditekan lama. Maaf untuk pertanyaan yang luas ini, tetapi saya tidak dapat menemukan yang lebih baik baik di Google maupun SO

Oscar Apeland
sumber

Jawaban:

220

Objective-C

Di myCollectionViewController.hfile Anda, tambahkan UIGestureRecognizerDelegateprotokol

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

di myCollectionViewController.mfile Anda :

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

Cepat

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

Cepat 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))
biara
sumber
1
itu sudah ada di jawaban: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];referensi di sini harap semua ini pantas mendapatkan jawaban yang benar: D
abbood
10
Untuk (setidaknya) ios7 Anda harus menambahkan lpgr.delaysTouchesBegan = YES;untuk menghindari didHighlightItemAtIndexPathpemicuan terlebih dahulu.
DynamicDan
7
Mengapa Anda menambahkan lpgr.delegate = self;? Ini berfungsi dengan baik tanpa delegasi, yang belum Anda sediakan.
Yevhen Dubinin
3
@abbood jawabannya berfungsi, tetapi saya tidak dapat menggulir ke atas dan ke bawah dalam tampilan koleksi (menggunakan jari lain) saat pengenal pers lama aktif. Apa yang memberi?
Pétur Ingi Egilsson
4
Secara pribadi, saya akan melakukannya UIGestureRecognizerStateBegan, jadi isyarat itu digunakan saat dikenali, bukan saat pengguna melepaskan jari mereka.
Jeffrey Sun
28

Kode yang sama @ abbood kode untuk Swift:

Dalam viewDidLoad:

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

Dan fungsinya:

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
    }

}

Jangan lupakan delegasi UIGestureRecognizerDelegate

Guilherme de Freitas
sumber
3
Berhasil, cukup perhatikan bahwa "handleLongPress:" harus diubah menjadi #selector (YourViewController.handleLongPress (_ :))
Joseph Geraghty
16
Berfungsi dengan baik, tetapi ubah UIGestureRecognizerState.Endedke UIGestureRecognizerState.Beganjika Anda ingin kode diaktifkan setelah durasi minimum berlalu, tidak hanya saat pengguna mengangkat jarinya.
Crashalot
11

Gunakan delegasi UICollectionView menerima acara pers lama

Anda harus menerapkan 3 metode di bawah ini.

//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
sumber
Catatan: Jika Anda mengembalikan false untuk shouldShowMenuForItemAtIndexPath, didSelectItemAtIndexPath juga akan dijalankan. Ini menjadi masalah bagi saya ketika saya menginginkan dua tindakan berbeda untuk pers lama vs pers tunggal.
ShannonS
itu adalah metode yang tidak digunakan lagi untuk saat ini, Anda dapat menggunakan - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (nonnull NSIndexPath *) indexPath point: (CGPoint) point;
Viktor Goltvyanitsa
8

Jawaban di sini untuk menambahkan pengenal isyarat longpress kustom benar namun menurut dokumentasi di sini : kelas induk dari UICollectionViewkelas menginstal a default long-press gesture recognizeruntuk menangani interaksi pengguliran sehingga Anda harus menautkan pengenal isyarat tap kustom Anda ke pengenal default yang terkait dengan tampilan koleksi Anda.

Kode berikut akan menghindari pengenal isyarat khusus Anda untuk mengganggu pengenal default:

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.dll
sumber
saya mengerti apa yang Anda katakan, tetapi tidak hitam dan putih, dokumentasi mengatakan: 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.jadi pengenal tekan lama default dibuat untuk menggulir .. yang menyiratkan itu harus disertai dengan gerakan vertikal .. OP tidak bertanya tentang perilaku semacam itu dan dia juga tidak mencoba untuk menggantinya
abbood
maaf karena terdengar defensif, tetapi saya telah menggunakan kode di atas dengan aplikasi iOS saya selama berbulan-bulan .. tidak dapat memikirkan satu kali pun kesalahan terjadi
abbood
@abbb itu bagus. Saya tidak tahu komponen kalender pihak ketiga yang digunakan PO tetapi saya pikir mencegah kesalahan bahkan jika menurut Anda itu tidak akan pernah terjadi tidak bisa menjadi ide yang buruk ;-)
tiguero
Jika Anda harus menunggu pengenal default gagal bukan berarti akan ada penundaan?
Crashalot
2
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

[cell addGestureRecognizer:longPress];

dan tambahkan metode seperti ini.

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

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

Untuk memiliki pengenal isyarat eksternal dan tidak bertentangan dengan pengenal isyarat internal di UICollectionView, Anda perlu:

Tambahkan pengenal gerakan Anda, atur dan tangkap referensi untuknya di suatu tempat (opsi terbaik ada di subkelas Anda jika Anda membuat subkelas UICollectionView)

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Override standar metode inisialisasi initWithFrame:collectionViewLayout:dan initWithCoder:dan menambahkan menyiapkan metode untuk Anda tekan lama isyarat recognizer

@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

Tulis metode penyiapan Anda sehingga dapat membuat pengenal gerakan tekan lama, setel delegasinya, atur dependensi dengan pengenal gerakan UICollectionView (jadi itu menjadi gerakan utama dan semua gerakan lainnya akan menunggu sampai gerakan itu gagal sebelum dikenali) dan menambahkan gerakan ke tampilan

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

Juga jangan lupa untuk mengimplementasikan metode UIGestureRecognizerDelegate yang gagal memberikan isyarat tersebut dan memungkinkan pengenalan secara bersamaan (Anda mungkin atau mungkin tidak perlu mengimplementasikannya, itu tergantung pada pengenal isyarat lain yang Anda miliki atau ketergantungan dengan pengenal isyarat internal)

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

    return NO;
}

kredensial untuk itu masuk ke implementasi internal LXReorderableCollectionViewFlowLayout

Dmitry Fantastik
sumber
Ini adalah tarian yang sama yang saya lakukan untuk klon Snapchat saya. ahhh cinta sejati.
benjaminhallock
1

Cepat 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)")
    }
}

Juga jangan lupa untuk mengimplementasikan UIGestureRecognizerDelegate dan memanggil setupLongGestureRecognizerOnCollection dari viewDidLoad atau di mana pun Anda perlu memanggilnya.

Renexandro
sumber
0

Mungkin, menggunakan UILongPressGestureRecognizer adalah solusi yang paling luas. Tapi saya menemui dua masalah yang mengganggu:

  • terkadang pengenal ini bekerja dengan cara yang salah saat kita menggerakkan sentuhan;
  • pengenal mencegat tindakan sentuh lainnya sehingga kami tidak dapat menggunakan panggilan balik sorotan dari UICollectionView kami dengan cara yang benar.

Izinkan saya menyarankan satu sedikit kekerasan, tetapi bekerja sesuai saran yang diperlukan:

Menyatakan deskripsi panggilan balik untuk klik lama pada sel kita:

typealias OnLongClickListener = (view: OurCellView) -> Void

Memperluas UICollectionViewCell dengan variabel (kita bisa menamakannya OurCellView, misalnya):

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

Menambahkan dua metode di kelas sel kita:

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

Dan acara sentuh utama di sini:

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

Kemudian di suatu tempat di pengontrol tampilan koleksi kami yang mendeklarasikan pemroses panggilan balik:

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

Dan terakhir di cellForItemAtIndexPath mengatur callback untuk sel kita:

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

Sekarang kita dapat menghentikan aksi klik panjang pada sel kita.

Andrei K.
sumber