Cử chỉ nhấn và giữ trên UICollectionViewCell


108

Tôi đang tự hỏi làm thế nào để thêm một trình nhận dạng cử chỉ nhấn và giữ vào một (lớp con của) UICollectionView. Tôi đọc trong tài liệu rằng nó được thêm vào theo mặc định, nhưng tôi không thể tìm ra cách.

Những gì tôi muốn làm là: Nhấn và giữ một ô ( tôi có một lịch từ github ), lấy ô nào được nhấn và sau đó thực hiện mọi việc với nó. Tôi cần biết ô nào được nhấn dài. Xin lỗi vì câu hỏi rộng này, nhưng tôi không thể tìm thấy gì tốt hơn trên google hoặc SO

Câu trả lời:


220

Objective-C

Trong myCollectionViewController.htệp của bạn, hãy thêm UIGestureRecognizerDelegategiao thức

@interface myCollectionViewController : UICollectionViewController<UIGestureRecognizerDelegate>

trong myCollectionViewController.mtệp của bạn :

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

Nhanh

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
nó đã có trong câu trả lời: UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath];tham khảo ở đây hy vọng tất cả công đức này một giải thưởng câu trả lời đúng: D
abbood

10
Đối với (ít nhất) ios7, bạn phải thêm lpgr.delaysTouchesBegan = YES;vào để tránh didHighlightItemAtIndexPathbị kích hoạt trước.
DynamicDan

7
Tại sao bạn lại thêm lpgr.delegate = self;? Nó hoạt động tốt mà không cần ủy quyền, mà bạn cũng chưa cung cấp.
Yevhen Dubinin

3
@abbood câu trả lời hoạt động, nhưng tôi không thể cuộn lên và xuống trong chế độ xem bộ sưu tập (sử dụng một ngón tay khác) trong khi trình nhận dạng báo chí dài đang hoạt động. Đưa cái gì?
Pétur Ingi Egilsson

4
Cá nhân tôi sẽ làm UIGestureRecognizerStateBeganvậy, vì vậy cử chỉ được sử dụng khi nó được nhận dạng, không phải khi người dùng thả ngón tay của họ.
Jeffrey Sun

28

Mã tương tự @ abbood's code cho Swift:

Trong viewDidLoad:

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

Và chức năng:

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
    }

}

Đừng quên đại biểu UIGestureRecognizerDelegate


3
Làm việc rất tốt, chỉ cần lưu ý rằng "handleLongPress:" nên được đổi thành #selector (YourViewController.handleLongPress (_ :))
Joseph Geraghty

16
Hoạt động tốt, nhưng hãy thay đổi UIGestureRecognizerState.Endedthành UIGestureRecognizerState.Begannếu bạn muốn mã kích hoạt khi thời lượng tối thiểu đã trôi qua, không chỉ khi người dùng nhấc ngón tay lên.
Crashalot

11

Sử dụng đại biểu của UICollectionView nhận sự kiện báo chí dài

Bạn phải cấy ghép 3 phương pháp dưới đây.

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

Lưu ý: Nếu bạn trả về false cho shouldShowMenuForItemAtIndexPath, didSelectItemAtIndexPath cũng sẽ được khởi chạy. Điều này trở thành vấn đề đối với tôi khi tôi muốn có hai thao tác khác nhau khi nhấn lâu và nhấn một lần.
ShannonS

hiện tại nó không còn được dùng nữa, bạn có thể sử dụng - (UIContextMenuConfiguration *) collectionView: (UICollectionView *) collectionView contextMenuConfigurationForItemAtIndexPath: (nonnull NSIndexPath *) indexPath point: (CGPoint) point;
Viktor Goltvyanitsa

8

Các câu trả lời ở đây để thêm trình nhận dạng cử chỉ nhấn phím dài tùy chỉnh là chính xác. Tuy nhiên, theo tài liệu ở đây : lớp cha của UICollectionViewlớp cài đặt a default long-press gesture recognizerđể xử lý các tương tác cuộn, vì vậy bạn phải liên kết trình nhận dạng cử chỉ nhấn tùy chỉnh của mình với trình nhận dạng mặc định được liên kết với chế độ xem bộ sưu tập của bạn.

Mã sau sẽ tránh trình nhận dạng cử chỉ tùy chỉnh của bạn can thiệp vào mã mặc định:

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

tôi thấy những gì bạn đang nói, nhưng nó không phải là màu đen và trắng, tài liệu cho biế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.vì vậy trình nhận dạng nhấn và giữ mặc định được tạo ra để cuộn .. có nghĩa là nó phải đi kèm với chuyển động thẳng đứng .. OP không hỏi về điều đó loại hành vi cũng không phải là anh cố gắng thay thế nó
abbood

xin lỗi vì nghe có vẻ phòng thủ, nhưng tôi đã sử dụng trên mã với ứng dụng iOS của tôi trong nhiều tháng .. không thể nghĩ ra một lần duy nhất một trục trặc đã xảy ra
abbood

@abbood ổn mà. Tôi không biết các thành phần của bên thứ ba lịch của PO đang sử dụng nhưng tôi nghĩ rằng việc ngăn ngừa một trục trặc ngay cả khi u nghĩ rằng nó không bao giờ có thể xảy ra không thể là một ý tưởng tồi ;-)
tiguero

Nếu bạn phải đợi trình nhận dạng mặc định không thành công, không có nghĩa là sẽ có độ trễ?
Crashalot

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

[cell addGestureRecognizer:longPress];

và thêm phương thức như thế này.

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

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

2

Để có bộ nhận dạng cử chỉ bên ngoài và không xung đột với các bộ nhận dạng cử chỉ bên trong trên UICollectionView, bạn cần:

Thêm trình nhận dạng cử chỉ của bạn, thiết lập nó và chụp một tham chiếu cho nó ở đâu đó (tùy chọn tốt nhất là trên lớp con của bạn nếu bạn phân lớp UICollectionView)

@interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    

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

@end

Ghi đè mặc định các phương pháp khởi tạo initWithFrame:collectionViewLayout:initWithCoder:và thêm thiết lập phương thức cho bạn báo chí dài cử chỉ 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

Viết phương thức thiết lập của bạn để nó khởi tạo trình nhận dạng cử chỉ nhấn và giữ, đặt nó là đại biểu, thiết lập các phần phụ thuộc với trình nhận dạng cử chỉ UICollectionView (vì vậy nó là cử chỉ chính và tất cả các cử chỉ khác sẽ đợi cho đến khi cử chỉ đó không thành công trước khi được nhận dạng) và thêm cử chỉ vào chế độ xem

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

Cũng đừng quên triển khai các phương thức UIGestureRecognizerDelegate không thực hiện được cử chỉ đó và cho phép nhận dạng đồng thời (bạn có thể cần hoặc có thể không cần triển khai, điều này phụ thuộc vào các trình nhận dạng cử chỉ khác mà bạn có hoặc phụ thuộc vào các trình nhận dạng cử chỉ nội bộ)

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

    return NO;
}

bằng chứng xác thực cho việc triển khai nội bộ LXReorderableCollectionViewFlowLayout


Đây cũng là điệu nhảy mà tôi đã làm cho bản sao Snapchat của mình. ahhh tình yêu đích thực.
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)")
    }
}

Cũng đừng quên triển khai UIGestureRecognizerDelegate và gọi setupLongGestureRecognizerOnCollection từ viewDidLoad hoặc bất cứ nơi nào bạn cần gọi nó.


0

Có lẽ, sử dụng UILongPressGestureRecognizer là giải pháp phổ biến nhất. Nhưng tôi gặp phải hai rắc rối khó chịu:

  • đôi khi trình nhận dạng này hoạt động không chính xác khi chúng tôi di chuyển chạm;
  • trình nhận dạng chặn các hành động chạm khác nên chúng tôi không thể sử dụng các lệnh gọi lại đánh dấu của UICollectionView theo cách phù hợp.

Hãy để tôi đề xuất một cách hơi thô bạo một chút, nhưng hoạt động như đề xuất bắt buộc:

Khai báo mô tả gọi lại cho nhấp chuột lâu vào ô của chúng tôi:

typealias OnLongClickListener = (view: OurCellView) -> Void

Mở rộng UICollectionViewCell với các biến (ví dụ: chúng ta có thể đặt tên là 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

Thêm hai phương thức trong lớp ô của chúng tôi:

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

Và ghi đè các sự kiện cảm ứng tại đây:

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

Sau đó, ở đâu đó trong bộ điều khiển chế độ xem bộ sưu tập của chúng tôi khai báo trình nghe gọi lại:

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

Và cuối cùng trong gọi lại thiết lập cellForItemAtIndexPath cho các ô của chúng tôi:

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

Bây giờ chúng tôi có thể chặn các hành động nhấp chuột dài trên các ô của chúng tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.