Довгий жест натискання на UICollectionViewCell


108

Мені було цікаво, як додати розпізнавальник жестів довгого натискання до (підкласу) UICollectionView. Я читав у документації, що вона додана за замовчуванням, але не можу зрозуміти, як.

Що я хочу зробити, це: довге натискання на клітинку (у мене є календар із Github ), дістаньте , яку клітинку натисніть, а потім виконайте з нею речі. Мені потрібно знати, яка клітина довго стискається. Вибачте за це широке запитання, але я не зміг знайти нічого кращого ні в Google, ні в SO

Відповіді:


220

Ціль-С

У свій myCollectionViewController.hфайл додайте UIGestureRecognizerDelegateпротокол

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

у вашому myCollectionViewController.mфайлі:

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

Швидкий

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

Швидкий 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
це вже у відповідь: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];посилання тут сподіваюся, що все це заслуговує на правильну нагороду відповіді: D
abbood

10
Для (принаймні) ios7 ви повинні додати, lpgr.delaysTouchesBegan = YES;щоб уникнути didHighlightItemAtIndexPathйого запуску спочатку.
DynamicDan

7
Чому ви додали lpgr.delegate = self;? Це прекрасно працює без делегата, якого ви також не надали.
Євген Дубінін

3
@abbood відповідь спрацьовує, але я не можу прокручувати вгору та вниз перегляд колекції (за допомогою іншого пальця), коли активний розпізнавач довгих натискань. Що дає?
Петур Інгі Егільссон

4
Особисто я б це зробив UIGestureRecognizerStateBegan, тому жест використовується, коли його розпізнають, а не коли користувач відпускає палець.
Джеффрі Нд

28

Той самий код @ abbood для Swift:

У viewDidLoad:

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

І функція:

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
    }

}

Не забудьте делегата UIGestureRecognizerDelegate


3
Працювало чудово, лише зауважимо, що "handleLongPress:" слід змінити на #selector (YourViewController.handleLongPress (_ :))
Джозеф Герайтті

16
Працює добре, але зміни UIGestureRecognizerState.Endedв , UIGestureRecognizerState.Beganякщо ви хочете , щоб код на вогонь , як тільки мінімальна тривалість пройшло, а не тільки тоді , коли користувач вибирає вгору його / її пальцем.
Crashalot

11

Скористайтеся делегатом UICollectionView отримати тривалу прес-подію

Ви повинні застосувати 3 метод нижче.

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

Примітка: Якщо ви повернете помилку для ShouldShowMenuForItemAtIndexPath, didSelectItemAtIndexPath також буде знято. Це стало для мене проблематичним, коли я хотів дві різні дії для тривалої преси проти однієї преси.
ShannonS

зараз застарілі методи, ви можете використовувати - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (nonnull NSIndexPath *) indexPath point: (CGPoint) point;
Віктор Гольтвяниця

8

Відповіді на додавання спеціального розпізнавача жестів longpress є правильними, проте згідно з документацією тут : батьківський клас UICollectionViewкласу встановлює adefault long-press gesture recognizer для обробки взаємодії прокрутки, тому ви повинні зв’язати свій власний розпізнавальний жест натискання з розпізнавачем за замовчуванням, пов’язаним з поданням вашої колекції.

Указаний нижче код не дозволить вашому користувальницькому розпізнавальнику жестів втручатися у стандартний:

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

я бачу, що ви говорите, але це не чорно-біле, документація говорить: 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.тому розпізнавач довгих натискань за замовчуванням робиться для прокрутки .. що означає, що він повинен супроводжуватися вертикальним рухом .. ОП не запитує про таку поведінку, і він не намагається її замінити
abbood

вибачте за звукозахисне звучання, але я використовував вищезазначений код у моєму додатку для iOS протягом місяців. Не можу придумати жодного разу, коли стався збій
abbood

@abbood це добре. Я не знаю стороннього компонента календаря, який використовує PO, але я думаю, що запобігання глюку, навіть якщо ти вважаєш, що це ніколи не може статися, не може бути поганою ідеєю ;-)
tiguero

Якщо вам потрібно чекати виходу з ладу розпізнавача за замовчуванням, чи не означає це, що буде затримка?
Crashalot

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

[cell addGestureRecognizer:longPress];

і додати такий спосіб.

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

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

2

Щоб мати зовнішній розпізнавальник жестів і не суперечити внутрішнім розпізнавачам жестів на UICollectionView, вам потрібно:

Додайте розпізнавальник жестів, налаштуйте його та захойте його десь (найкращий варіант - у вашому підкласі, якщо ви підкласируєте UICollectionView)

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Override по замовчуванням методи ініціалізації initWithFrame:collectionViewLayout:і initWithCoder:і додати настройки метод для вас довгий прес жест распознаватель

@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

Напишіть спосіб налаштування, щоб він створив функцію розпізнавання жестів довгим натисканням, встановіть його делегат, встановлення залежностей з розпізнавачем жестів UICollectionView (так що це буде основним жестом, а всі інші жести будуть чекати, поки цей жест не вдасться розпізнати) та додайте жест до подання

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

Також не забудьте реалізувати методи UIGestureRecognizerDelegate, які не дозволяють цьому жесту і дозволяють одночасно розпізнавати (ви можете або не знадобитися для його здійснення, це залежить від інших розпізнавачів жестів або залежностей від внутрішніх розпізнавачів жестів)

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

    return NO;
}

Повноваження на це спрямовані на внутрішню реалізацію LXReorderableCollectionViewFlowLayout


Це той самий танець, який я робив для свого клона Snapchat. ах справжня любов.
Бенджаміналлок

1

Швидкий 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)")
    }
}

Також не забудьте реалізувати UIGestureRecognizerDelegate та налаштування викликуLongGestureRecognizerOnCollection з viewDidLoad або куди потрібно зателефонувати.


0

Можливо, використання UILongPressGestureRecognizer є найпоширенішим рішенням. Але я зустрічаю з нею дві набридливі неприємності:

  • іноді цей розпізнавач працює неправильно, коли ми рухаємося своїм дотиком;
  • розпізнавач перехоплює інші сенсорні дії, тому ми не можемо належним чином використовувати зворотні дзвінки UICollectionView.

Дозвольте запропонувати один трохи брутальну силу, але працюючи так, як вимагається пропозиція:

Оголошення опису зворотного дзвінка для довгого натискання на нашу клітинку:

typealias OnLongClickListener = (view: OurCellView) -> Void

Розширення UICollectionViewCell змінними (ми можемо назвати його 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

Додавання двох методів до нашого класу клітин:

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

І тут важливі сенсорні події:

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

Потім десь у контролері нашої колекції подання оголошує слухача зворотного дзвінка:

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

І, нарешті, у налаштуваннях зворотного виклику для CellForItemAtIndexPath для наших комірок:

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

Тепер ми можемо перехоплювати дії довгих клацань на своїх клітинках.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.