【问题标题】:Long press gesture on UICollectionViewCellUICollectionViewCell 上的长按手势
【发布时间】:2013-09-21 19:48:25
【问题描述】:

我想知道如何将长按手势识别器添加到 UICollectionView(的子类)。我在文档中读到它是默认添加的,但我不知道如何。

我想做的是: 长按一个单元格(I have a calendar thingy from github),获取点击了哪个单元格,然后对其进行处理。我需要知道长按的是哪个单元格。抱歉这个广泛的问题,但我在谷歌或 SO 上找不到更好的东西

【问题讨论】:

    标签: ios objective-c uicollectionview


    【解决方案1】:

    目标-C

    在您的 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))
    

    【讨论】:

    • 它已经在答案中了:UICollectionViewCell* cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 参考here 希望这一切都值得一个正确的答案奖:D
    • 对于(至少)ios7,您必须添加lpgr.delaysTouchesBegan = YES; 以避免首先触发didHighlightItemAtIndexPath
    • 为什么加lpgr.delegate = self;?没有委托,它也可以正常工作,您也没有提供。
    • @abbood 答案有效,但是当长按识别器处于活动状态时,我无法在集合视图中上下滚动(使用另一根手指)。什么给了?
    • 就我个人而言,我会使用UIGestureRecognizerStateBegan,所以手势在被识别时使用,而不是在用户松开手指时使用。
    【解决方案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;
        }
    }
    

    【讨论】:

      【解决方案3】:

      这里添加自定义长按手势识别器的答案是正确的然而根据文档hereUICollectionView 类的父类安装了一个default 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. 所以默认的长按识别器是为滚动而设计的......这意味着它必须伴随垂直运动.. OP 没有询问这种行为,也没有试图替换它
      • 抱歉听起来像是防御性的,但我几个月来一直在我的 iOS 应用程序中使用above 代码.. 想不出任何一次故障发生
      • @abbood 没关系。我不知道 PO 正在使用的第三方日历组件,但我认为即使您认为它永远不会发生,防止故障也不是一个坏主意 ;-)
      • 如果您必须等待默认识别器失败,这是否意味着会有延迟?
      【解决方案4】:

      同样的代码@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

      【讨论】:

      • 效果很好,只是需要注意“handleLongPress:”应该改为#selector(YourViewController.handleLongPress(_:))
      • 效果很好,但如果您希望代码在最短持续时间过去后触发,而不仅仅是在用户拿起他/她的手指时触发,请将 UIGestureRecognizerState.Ended 更改为 UIGestureRecognizerState.Began
      • self.myCollectionView?.indexPathForItem(at: p) 返回致命错误:当单击集合视图中的单元格以外的任何其他位置时,在展开可选值时意外发现 nil
      • 此方法抛出致命错误:当您单击 CollectionView 并且没有单元格时,在展开可选值错误时意外发现 nil。如何解决此错误?
      【解决方案5】:

      要拥有外部手势识别器并且不与 UICollectionView 上的内部手势识别器冲突,您需要:

      添加你的手势识别器,设置它并在某处捕获它的引用(如果你将 UICollectionView 子类化,最好的选择是在你的子类上)

      @interface UICollectionViewSubclass : UICollectionView <UIGestureRecognizerDelegate>    
      
      @property (strong, nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer;   
      
      @end
      

      覆盖默认初始化方法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 克隆做的同样的舞蹈。啊,真爱。
      【解决方案6】:

      使用 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 返回 false,didSelectItemAtIndexPath 也将被启动。当我想要长按和单按两种不同的动作时,这对我来说就成了问题。
      • 现在是不推荐使用的方法,你可以使用 - (UIContextMenuConfiguration *)collectionView:(UICollectionView *)collectionView contextMenuConfigurationForItemAtIndexPath:(nonnull NSIndexPath *)indexPath point:(CGPoint)point;
      【解决方案7】:

      也许,使用 UILongPressGestureRecognizer 是最普遍的解决方案。但是我遇到了两个烦人的麻烦:

      • 有时,当我们移动触摸时,此识别器会以不正确的方式工作;
      • 识别器会拦截其他触摸动作,因此我们无法以正确的方式使用 UICollectionView 的高亮回调。

      让我建议一个有点蛮力,但按要求工作的建议:

      为长按我们的单元格声明一个回调描述:

      typealias OnLongClickListener = (view: OurCellView) -&gt; 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
      }
      

      现在我们可以拦截单元格上的长按操作。

      【讨论】:

        【解决方案8】:

        斯威夫特 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 并从 viewDidLoad 或任何你需要调用它的地方调用 setupLongGestureRecognizerOnCollection。

        【讨论】:

        • 此方法抛出致命错误:当您单击 CollectionView 并且没有单元格时,在展开可选值错误时意外发现 nil。如何解决此错误?
        • 为什么在你没有实现这个方法的时候还要设置委托?
        【解决方案9】:

        更简单的解决方案。

        在您的 cellForItemAt 委托中(为以后设置 .tag 属性):

        cell.gestureRecognizers?.removeAll()
        cell.tag = indexPath.row
        let directFullPreviewer = UILongPressGestureRecognizer(target: self, action: #selector(directFullPreviewLongPressAction))
        cell.addGestureRecognizer(directFullPreviewer)
        

        以及 longPress 的回调:

        @objc func directFullPreviewLongPressAction(g: UILongPressGestureRecognizer)
        {
            if g.state == UIGestureRecognizer.State.began
            {
                // Get index as g.view.tag and that's it
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-09-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多