【问题标题】:How can I detect a double tap on a certain cell in UITableView?如何检测 UITableView 中某个单元格的双击?
【发布时间】:2010-11-05 02:23:23
【问题描述】:

如何检测UITableView 中某个单元格的双击?

如果用户进行了一次触摸,我想执行一个操作,如果用户进行了两次触摸,我想执行另一个操作?我还需要知道进行触摸的索引路径。

我怎样才能实现这个目标?

谢谢。

【问题讨论】:

  • 您是指双击还是多点触控?
  • 仅从 HIG 的角度来看,您可能想考虑使用附件按钮而不是需要双击?我不知道使用场景,但您可能需要向您的用户解释这一点。
  • 现代方法是使用 UIGestureRecognizer。在界面生成器中添加它。您可以设置抽头数。您以目标/操作方式连接 UIGestureRecognizer,就像按钮一样。

标签: iphone cocoa-touch uitableview


【解决方案1】:

如果您不想创建UITableView 的子类,请使用带有表视图didSelectRowAtIndex: 的计时器

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //checking for double taps here
    if(tapCount == 1 && tapTimer != nil && tappedRow == indexPath.row){
        //double tap - Put your double tap code here
        [tapTimer invalidate];
        [self setTapTimer:nil];

        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Double Tap" message:@"You double-tapped the row" delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
        [alert show];
        [alert release];
    }
    else if(tapCount == 0){
        //This is the first tap. If there is no tap till tapTimer is fired, it is a single tap
        tapCount = tapCount + 1;
        tappedRow = indexPath.row;
        [self setTapTimer:[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(tapTimerFired:) userInfo:nil repeats:NO]];
    }
    else if(tappedRow != indexPath.row){
        //tap on new row
        tapCount = 0;
        if(tapTimer != nil){
            [tapTimer invalidate];
            [self setTapTimer:nil];
        }
    }
}

- (void)tapTimerFired:(NSTimer *)aTimer{
    //timer fired, there was a single tap on indexPath.row = tappedRow
    if(tapTimer != nil){
        tapCount = 0;
        tappedRow = -1;
    }
}

HTH

【讨论】:

  • 您介意发布更多代码吗?你在哪里设置 tapTimer、tappedRow 和 tapCount?谢谢!
  • 将计时器设置为幻数 0.2 是否安全?这是双击之间通常的时间限制吗?这个数字可以改变吗?
  • @jlstrecker 没有人通常会花费比这更多的时间来双击。只是我试验了几个后得出的一个随机数。
  • 我认为有一个 tapCount = 0;在第一个 if 子句中丢失,就在显示警报之前(或者可能更准确地说之后?)
  • 为什么这比单击和双击手势识别器更好?
【解决方案2】:

在您的UITableView 类中重写此方法

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
 {

     if(((UITouch *)[touches anyObject]).tapCount == 2)
    {
    NSLog(@"DOUBLE TOUCH");
    }
    [super touchesEnded:touches withEvent:event];
}

【讨论】:

  • 你能解释一下我如何完全覆盖该方法吗?我创建了一个继承自 UITableView 的对象并添加了您推荐的方法。但是这个方法不会被调用。谢谢。
  • 本身不是 necropost,但这正是我一直在寻找的东西 - 谢谢!对我来说只有一个问题。如果有问题的 UITableView 是 UITabBar 的 moreNavigationController 中的那个呢? :(
  • 你也可以把它添加到你的UITableViewCell类中。
【解决方案3】:

在您的 UITableView 子类中,执行以下操作:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch* touch in touches) {
        if (touch.tapCount == 2)
        {
            CGPoint where = [touch locationInView:self];
            NSIndexPath* ip = [self indexPathForRowAtPoint:where];
            NSLog(@"double clicked index path: %@", ip);

            // do something useful with index path 'ip'
        }
    }

    [super touchesEnded:touches withEvent:event];
}

【讨论】:

    【解决方案4】:

    先定义:

    int tapCount;
    NSIndexPath *tableSelection;
    

    作为 .h 文件中的类级别变量并进行所有必要的设置。那么……

    - (void)tableView(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        tableSelection = indexPath;
        tapCount++;
    
        switch (tapCount) {
            case 1: //single tap
                [self performSelector:@selector(singleTap) withObject: nil afterDelay: .4];
                break;
            case 2: //double tap
                [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(singleTap) object:nil];
                [self performSelector:@selector(doubleTap) withObject: nil];
                break;
            default:
                break;
        }
    }
    
    #pragma mark -
    #pragma mark Table Tap/multiTap
    
    - (void)singleTap {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Alert" message:@"Single tap detected" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];   
        tapCount = 0;
    }
    
    - (void)doubleTap {
        NSUInteger row = [tableSelection row];
        companyName = [self.suppliers objectAtIndex:row]; 
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Alert" message:@"DoubleTap" delegate:nil cancelButtonTitle:@"Yes" otherButtonTitles: nil];
        [alert show];
        tapCount = 0;
    }
    

    【讨论】:

    【解决方案5】:
    if([touch tapCount] == 1)
    {
        [self performSelector:@selector(singleTapRecevied) withObject:self afterDelay:0.3];
    
    } else if ([touch tapCount] == 2)
      {        
        [TapEnableImageView cancelPreviousPerformRequestsWithTarget:self selector:@selector(singleTapRecevied) object:self]; 
    }
    

    使用 performSelector 调用选择器而不是使用计时器。这解决了@V1ru8 提到的问题。

    【讨论】:

      【解决方案6】:

      我选择通过覆盖UITableViewCell 来实现它。

      MyTableViewCell.h

      @interface MyTableViewCell : UITableViewCell
      
      @property (nonatomic, assign) int numberOfClicks;
      
      @end
      

      MyTableViewCell.m

      @implementation MyTableViewCell
      
      - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
         UITouch *aTouch = [touches anyObject];
         self.numberOfClicks = [aTouch tapCount];
         [super touchesEnded:touches withEvent:event];
      }
      

      TableViewController.m

      - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
      
         MyTableViewCell *myCell = (MyTableViewCell*) [self.tableView cellForRowAtIndexPath:indexPath];
      
         NSLog(@"clicks:%d", myCell.numberOfClicks);
      
         if (myCell.numberOfClicks == 2) {
             NSLog(@"Double clicked");
         }
      }
      

      【讨论】:

        【解决方案7】:

        比较答案中的 Swift 3 解决方案。无需任何扩展,只需添加此代码即可。

        override func viewDidLoad() {
            viewDidLoad()
        
            let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(sender:)))
            doubleTapGestureRecognizer.numberOfTapsRequired = 2
            tableView.addGestureRecognizer(doubleTapGestureRecognizer)
        
            let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(sender:)))
            tapGestureRecognizer.numberOfTapsRequired = 1
            tapGestureRecognizer.require(toFail: doubleTapGestureRecognizer)
            tableView.addGestureRecognizer(tapGestureRecognizer)
        }
        
        func handleTapGesture(sender: UITapGestureRecognizer) {
            let touchPoint = sender.location(in: tableView)
            if let indexPath = tableView.indexPathForRow(at: touchPoint) {
                print(indexPath)
            }
        }
        
        func handleDoubleTap(sender: UITapGestureRecognizer) {
            let touchPoint = sender.location(in: tableView)
            if let indexPath = tableView.indexPathForRow(at: touchPoint) {
                print(indexPath)
            }
        }
        

        【讨论】:

        • 您是否必须在 handleTapGesture 方法中添加任何延迟?我觉得 handleTapGesture 会在检测到第二次点击之前执行,因此 handleTapGesture 不会有任何时间失败。不过我有点菜鸟,所以如果我没有正确理解,请解释一下。
        【解决方案8】:

        另一个答案

        int touches;
        
        - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
        {
          touches++;
        
            if(touches==2){
               //your action
            }
        }
        
        - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
        {
            touches=0;
        }
        

        【讨论】:

        • 有史以来最好和最短的答案!
        【解决方案9】:

        您可能需要继承 UITableView 并覆盖任何适当的触摸事件(touchesBegan:withEvent;、touchesEnded:withEvent 等)检查事件以查看有多少触摸,并执行您的自定义行为。不要忘记调用UITableView's touch 方法,否则您将无法获得默认行为。

        【讨论】:

          【解决方案10】:

          根据@lostInTransit,我在 Swift 中准备了代码

          var tapCount:Int = 0
          var tapTimer:NSTimer?
          var tappedRow:Int?
          
          override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
              //checking for double taps here
              if(tapCount == 1 && tapTimer != nil && tappedRow == indexPath.row){
                  //double tap - Put your double tap code here
                  tapTimer?.invalidate()
                  tapTimer = nil
              }
              else if(tapCount == 0){
                  //This is the first tap. If there is no tap till tapTimer is fired, it is a single tap
                  tapCount = tapCount + 1;
                  tappedRow = indexPath.row;
                  tapTimer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: "tapTimerFired:", userInfo: nil, repeats: false)
              }
              else if(tappedRow != indexPath.row){
                  //tap on new row
                  tapCount = 0;
                  if(tapTimer != nil){
                      tapTimer?.invalidate()
                      tapTimer = nil
                  }
              }
          }
          
          func tapTimerFired(aTimer:NSTimer){
          //timer fired, there was a single tap on indexPath.row = tappedRow
              if(tapTimer != nil){
                  tapCount = 0;
                  tappedRow = -1;
              }
          }
          

          【讨论】:

            【解决方案11】:

            注意:请参阅下面的 cmets 以了解虽然此解决方案对我有用,但它仍然可能不是一个好主意。

            创建UITableViewUITableViewCell 的子类(以及使用计时器)的替代方法是使用类别扩展UITableViewCell 类,例如(使用@oxigen 的答案,在这种情况下为单元格而不是表格):

            @implementation UITableViewCell (DoubleTap)
            - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
            {
                if(((UITouch *)[touches anyObject]).tapCount == 2)
                {
                    NSLog(@"DOUBLE TOUCH");
                }
                [super touchesEnded:touches withEvent:event];
            }
            @end
            

            这样您就不必用新的类名重命名UITableViewCell 的现有实例(将扩展该类的所有实例)。

            请注意,现在super 在这种情况下(这是一个类别)不是指UITableView,而是指它的超级UITView。但是对touchesEnded:withEvent: 的实际方法调用在UIResponder 中(其中UITViewUITableViewCell 都是子类),所以没有区别。

            【讨论】:

            • 覆盖类别中的现有方法可能有效,也可能无效。
            • 原来的UITableViewCell 不是一个类别,所以我看不出这里会有什么影响。但是我已经更新了答案的最后一部分以使其更加清晰。
            • 你怎么知道苹果没有把实现分成几类?
            • 当你编写一个类别时,你也在你自己的文件中定义它。那么你在使用类之前检查每个头文件吗?即使方法未在类别中定义:它也可以在任何未来版本中更改。只是永远不要使用类别来覆盖。
            【解决方案12】:

            这是我的完整解决方案:

            CustomTableView.h

            //
            //  CustomTableView.h
            //
            
            #import <UIKit/UIKit.h>
            
            @interface CustomTableView : UITableView
            
                // Nothing needed here
            
            @end
            

            CustomTableView.m

            //
            //  CustomTableView.m
            //
            
            #import "CustomTableView.h"
            
            @implementation CustomTableView
            
            
            //
            // Touch event ended
            //
            - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
            {
            
                // For each event received
                for (UITouch * touch in touches) {
            
                    NSIndexPath * indexPath = [self indexPathForRowAtPoint: [touch locationInView:self] ];
            
                    // One tap happened
                    if([touch tapCount] == 1)
                    {
                        // Call the single tap method after a delay
                        [self performSelector: @selector(singleTapReceived:)
                                   withObject: indexPath
                                   afterDelay: 0.3];
                    }
            
            
                    // Two taps happened
                    else if ([touch tapCount] == 2)
                    {
                        // Cancel the delayed call to the single tap method
                        [NSObject cancelPreviousPerformRequestsWithTarget: self
                                                                 selector: @selector(singleTapReceived:)
                                                                   object: indexPath ];
            
                        // Call the double tap method instead
                        [self performSelector: @selector(doubleTapReceived:)
                                   withObject: indexPath ];
                    }
            
            
                }
            
                // Pass the event to super
                [super touchesEnded: touches
                          withEvent: event];
            
            }
            
            
            //
            // Single Tap
            //
            -(void) singleTapReceived:(NSIndexPath *) indexPath
            {
                NSLog(@"singleTapReceived - row: %ld",(long)indexPath.row);
            }
            
            
            //
            // Double Tap
            //
            -(void) doubleTapReceived:(NSIndexPath *) indexPath
            {
                NSLog(@"doubleTapReceived - row: %ld",(long)indexPath.row);
            }
            
            
            
            @end
            

            【讨论】:

              【解决方案13】:

              oxigen 答案的改进。

              - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
                  UITouch *touch = [touches anyObject];
                  if(touch.tapCount == 2) {
                      CGPoint touchPoint = [touch locationInView:self];
                      NSIndexPath *touchIndex = [self indexPathForRowAtPoint:touchPoint];
                      if (touchIndex) {
                          // Call some callback function and pass 'touchIndex'.
                      }
                  }
                  [super touchesEnded:touches withEvent:event];
              }
              

              【讨论】:

                【解决方案14】:

                此解决方案仅适用于 UICollectionView 或 UITableView 的单元格。

                首先声明这些变量

                int number_of_clicks;

                BOOL thread_started;

                然后将此代码放入您的 didSelectItemAtIndexPath 中

                ++number_of_clicks;
                if (!thread_started) {
                
                    thread_started = YES;
                
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                                 0.25 * NSEC_PER_SEC),
                                   dispatch_get_main_queue(),^{
                
                                       if (number_of_clicks == 1) {
                                           ATLog(@"ONE");
                                       }else if(number_of_clicks == 2){
                                           ATLog(@"DOUBLE");
                                       }
                
                                       number_of_clicks = 0;
                                       thread_started = NO;
                
                                   });
                
                        }
                

                0.25 是两次点击之间的延迟。我认为 0.25 非常适合检测这种类型的点击。现在您只能分别检测一次单击和两次单击。祝你好运

                【讨论】:

                  猜你喜欢
                  • 2012-09-29
                  • 2018-08-30
                  • 2014-07-22
                  • 1970-01-01
                  • 2012-09-12
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多