【问题标题】:How to recognize oneTap/doubleTap at moment?目前如何识别 oneTap/doubleTap?
【发布时间】:2011-11-08 14:05:16
【问题描述】:

我知道使用 Apple API 过滤 oneTap/doubleTap。代码如下。

UITapGestureRecognizer *doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc]
                        initWithTarget:self action:@selector(handleDoubleTap:)];
doubleTapGestureRecognizer.numberOfTapsRequired = 2;


[self addGestureRecognizer:doubleTapGestureRecognizer];


UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
singleTapGestureRecognizer.numberOfTapsRequired = 1;

**[singleTapGestureRecognizer requireGestureRecognizerToFail: doubleTapGestureRecognizer];**

[self addGestureRecognizer:singleTapGestureRecognizer];

但是 oneTap/doubleTap checkDelayTime 感觉太长了(大约 0.5 秒?)。 一般App用户的反应是很快的。虽然 0.5 秒通常很短。但在移动设备环境中是长期的,因为用户的反应非常重要。

言归正传,YouTubeApp 有一个非常完美的算法来过滤 oneTap/doubleTap。 oneTap-doubleTap checkDelay 是 VeryVeryShort 的完美优化。

oneTap(显示/隐藏控制栏)

doubleTap(完整/默认 videoScreenSize)

如何像 YoutubeApp 一样实现?关于 oneTap-doubleTap 过滤不使用 requireGestureRecognizerToFail 选择器。关于很短的延迟 oneTap-doubleTap 区分。

我认为 YoutubeApp 没有使用 requireGestureRecognizer 选择器。

【问题讨论】:

    标签: iphone uigesturerecognizer


    【解决方案1】:

    最简单的方法是继承 UITapGestureRecognizer 而不是一般的 UIGestureRecognizer。

    像这样:

    #import <UIKit/UIGestureRecognizerSubclass.h>
    
    #define UISHORT_TAP_MAX_DELAY 0.2
    @interface UIShortTapGestureRecognizer : UITapGestureRecognizer
    
    @end
    

    然后简单地实现:

    @implementation UIShortTapGestureRecognizer
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        [super touchesBegan:touches withEvent:event];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(UISHORT_TAP_MAX_DELAY * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
        {
            // Enough time has passed and the gesture was not recognized -> It has failed.
            if  (self.state != UIGestureRecognizerStateRecognized)
            {
                self.state = UIGestureRecognizerStateFailed;
            }
        });
    }
    @end
    

    【讨论】:

    • 有助于指出这需要#import
    • 感谢@barfoon 的提示,我已经相应地编辑了答案。
    • 使用识别器的绝佳解决方案 - 如果您有其他手势的现有识别器,那么点击是一种特殊情况似乎很笨重。
    • 正如@Klaas 指出的那样,您可以在 SKScene 子类的 didMoveToView 中执行类似的操作(如果在 SpriteKit 中工作): UIShortTapGestureRecognizer *doubleTapRecognizer = [[UIShortTapGestureRecognizer alloc] initWithTarget:self action:@selector(双击:)]; doubleTapRecognizer.numberOfTapsRequired = 2; [self.view addGestureRecognizer:doubleTapRecognizer]; ... [singleTapRecognizer 需要GestureRecognizerToFail:doubleTapRecognizer]; ...
    • @bcattle 这里没有泄漏。该块不是自己保留的,是dispatch_after方法保留的。
    【解决方案2】:

    这在没有手势识别器的情况下最容易做到。然后你可以控制延迟。下面的代码是我在一个项目中使用的 Apple 原始文档的变体。我有blog post that talks about it as well

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
     UITouch *touch = [touches anyObject];
    if (touch.tapCount == 2) {
    //This will cancel the singleTap action
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    }
    
    }
    
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
     UITouch *touch = [touches anyObject];
    if (touch.tapCount == 1) {
      //if they tapped within the coin then place the single tap action to fire after a delay of 0.3
      if (CGRectContainsPoint(coin.frame,[touch locationInView:self.view])){
        //this is the single tap action being set on a delay
      [self performSelector:@selector(onFlip) withObject:nil afterDelay:0.3];
      }else{
       //I change the background image here
      }
     } else if (touch.tapCount == 2) {
      //this is the double tap action
      [theCoin changeCoin:coin];
     }
    }
    

    【讨论】:

    • 这是一个非常好的解决方案,但我宁愿尽可能使用手势识别器。您可以使用我下面的解决方案使用类似的方法和更少的代码来自定义延迟。
    【解决方案3】:

    您只需添加额外的代码行即可使用 requireGestureRecognizerToFail

    [singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
    

    然后整个代码变成:

    UITapGestureRecognizer *doubleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(beginComicTransitions:)] autorelease];    
    doubleTapRecognizer.numberOfTapsRequired = 2;
    doubleTapRecognizer.numberOfTouchesRequired = 1;
    doubleTapRecognizer.delegate = self;   
    
    UITapGestureRecognizer *singleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(bringMenu:)] autorelease];    
    singleTapRecognizer.numberOfTapsRequired = 1;
    singleTapRecognizer.numberOfTouchesRequired = 1;
    singleTapRecognizer.delegate = self;
    
    [singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
    

    这里的requireGestureRecognizerToFail 表示:

    • 如果不识别双击,则识别单击
    • 如果识别双击,将无法识别单击

    swift版本代码为:

        let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTapped:")
        doubleTap.numberOfTapsRequired = 2
        doubleTap.numberOfTouchesRequired = 1
        self.scrollView.addGestureRecognizer(doubleTap)
    
        let singleTap = UITapGestureRecognizer(target: self, action: "singleTap:")
        singleTap.numberOfTapsRequired = 1
        singleTap.numberOfTouchesRequired = 1
        self.scrollView.addGestureRecognizer(singleTap)
    
        singleTap.requireGestureRecognizerToFail(doubleTap)
    

    【讨论】:

    • 抱歉没有读到你的问题的结尾......你说你不想使用 requireGestureRecognizer
    • 你不需要设置delegate = self,你不用代理
    【解决方案4】:

    这是一个简单的双击自定义手势识别器,您可以在其中指定两次点击之间的最大允许时间。这是基于@Walters 的回答。

    PbDoubleTapGestureRecognizer.h:

    @interface PbDoubleTapGestureRecognizer : UIGestureRecognizer
    
    @property (nonatomic) NSTimeInterval maximumDoubleTapDuration;
    
    @end
    

    PbDoubleTapGestureRecognizer.m:

    #import "PbDoubleTapGestureRecognizer.h"
    #import <UIKit/UIGestureRecognizerSubclass.h>
    
    @interface PbDoubleTapGestureRecognizer ()
    @property (nonatomic) int tapCount;
    @property (nonatomic) NSTimeInterval startTimestamp;
    @end
    
    @implementation PbDoubleTapGestureRecognizer
    
    - (id)initWithTarget:(id)target action:(SEL)action {
        self = [super initWithTarget:target action:action];
        if (self) {
            _maximumDoubleTapDuration = 0.3f; // assign default value
        }
        return self;
    }
    
    -(void)dealloc {
        [NSObject cancelPreviousPerformRequestsWithTarget:self];
    }
    
    - (void)reset {
        [super reset];
    
        [NSObject cancelPreviousPerformRequestsWithTarget:self];
    
        self.tapCount = 0;
        self.startTimestamp = 0.f;
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        [super touchesBegan:touches withEvent:event];
    
        if (touches.count != 1 ) {
            self.state = UIGestureRecognizerStateFailed;
        } else {
            if (self.tapCount == 0) {
                self.startTimestamp = event.timestamp;
                [self performSelector:@selector(timeoutMethod) withObject:self afterDelay:self.maximumDoubleTapDuration];
            }
            self.tapCount++;
        }
    }
    
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
        [super touchesMoved:touches withEvent:event];
    }
    
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
        [super touchesEnded:touches withEvent:event];
    
        if (self.tapCount > 2) {
            self.state = UIGestureRecognizerStateFailed;
        } else if (self.tapCount == 2 && event.timestamp < self.startTimestamp + self.maximumDoubleTapDuration) {
            [NSObject cancelPreviousPerformRequestsWithTarget:self];
            NSLog(@"Recognized in %f", event.timestamp - self.startTimestamp);
            self.state = UIGestureRecognizerStateRecognized;
        }
    }
    
    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
        [super touchesCancelled:touches withEvent:event];
        self.state = UIGestureRecognizerStateFailed;
    }
    
    - (void)timeoutMethod {
        self.state = UIGestureRecognizerStateFailed;
    }
    
    @end
    

    你可以这样使用它:

    PbDoubleTapGestureRecognizer *doubleTapGr = [[PbDoubleTapGestureRecognizer alloc]initWithTarget:self action:@selector(_doubleTapAction)];
    doubleTapGr.maximumDoubleTapDuration = 0.4;
    [yourView addGestureRecognizer:doubleTapGr];
    

    您可以将其与requireGestureRecognizerToFail: 结合使用以获得所要求的行为。

    【讨论】:

    • 请注意,这也会识别 TouchDownFirstFinger、TouchDownSecondFinger、TouchUpFirstFinger、TouchUpSecondFinger。
    【解决方案5】:

    Swift 3.1 版本的eladleb's 答案。

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
            super.touchesBegan(touches, with: event)
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
                if self?.state != .recognized {
                    self?.state = .failed
                }
            }
        }
    

    【讨论】:

      【解决方案6】:

      如果您已经有一个附加到按钮或视图的操作,这里有一个更简单的答案。首先,更改 IBAction 以使其包含 UIEvent(不要忘记将其重新连接到您的按钮或情节提要中的视图):

      -(IBAction)buttonAction:(id)sender forEvent:(UIEvent*)event
      

      接下来,您可以在您的事件中捕获触摸,然后轻松测试点击次数:

      -(IBAction)buttonAction:(id)sender forEvent:(UIEvent*)event {
      
           UITouch* firstTouch = nil;
           if ((nil != ((firstTouch = event.allTouches.allObjects.firstObject)))
               && (2 == firstTouch.tapCount))
           {
               // do something for double-tap
           } else {
               // do something for single-tap
           }
      }
      

      您可以将此解决方案扩展为针对不同事件参数的其他情况,例如长按。

      【讨论】:

        【解决方案7】:
        @interface NaMeClass ()
        
        @property (nonatomic, strong) UITapGestureRecognizer * singleTap;
        @property (nonatomic, strong) NSTimer *timer;
        
        @end
        

        //...代码...

        //viewDidLoad
        self.singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapIcon:)];
        self.singleTap.numberOfTapsRequired = 1;
        self.singleTap.cancelsTouchesInView = YES;
        self.singleTap.delaysTouchesBegan = YES;
        [self addGestureRecognizer:self.singleTap];
        
        //.....code
        
        -(void)tapIcon:(UITapGestureRecognizer *)tapGesture
        {
            if (tapGesture.state == UIGestureRecognizerStateEnded){
                if (!self.timer) {
                    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.2
                                 target:self selector:@selector(singleTap) userInfo:nil repeats:NO];
                }else{
                    [self doubleTap];
             }
        }
        

        }

        -(void)singleTap{
            [self.timer invalidate];
            self.timer = nil;
            NSLog(@"1111111111111");
        }
        
        -(void)doubleTap{
            [self.timer invalidate];
            self.timer = nil;
            NSLog(@"22222222222");
        }
        

        【讨论】:

          猜你喜欢
          • 2016-05-13
          • 2021-07-17
          • 2021-12-14
          • 2012-11-18
          • 1970-01-01
          • 1970-01-01
          • 2022-01-23
          • 2022-01-23
          相关资源
          最近更新 更多