如果 UIGestureRecognizer 触发,如何取消按钮点击?

发布于 2024-11-11 18:16:02 字数 737 浏览 7 评论 0原文

更新:问题似乎是对另一个 GestureRecognizer 的依赖失败。请参阅此问题下面的评论和测试项目!

在我的 iPhone 应用程序中,我有一个包含多个 UIButtons 作为子视图的视图。该视图还有一个 UITapGestureRecognizer,它正在监听两根手指的点击。

当在视图上发生两指点击时,我不希望按钮对点击做出反应,即使其中一根手指位于按钮内。我认为这就是“cancelsTouchesInView”的用途,但这不起作用。

我现在的问题是:当识别手势时,如何告诉我的按钮忽略点击?

编辑:这是我的手势识别器。

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapped:)];
[doubleTap setNumberOfTouchesRequired:2];
[doubleTap setNumberOfTapsRequired:1];
[doubleTap setCancelsTouchesInView:YES];
[doubleTap setDelaysTouchesBegan:YES];
[doubleTap setDelaysTouchesEnded:YES];
[self.view addGestureRecognizer:doubleTap];
[doubleTap release];

Update: The problem seems to be the dependency on another GestureRecognizer to fail. See comments and test project below this question!

In my iPhone app I have a view with multiple UIButtons as subviews. The view also has a UITapGestureRecognizer which is listening for taps with two fingers.

When a two-finger-tap occurs on the view I don't want the buttons to react to the tap, even if one of the fingers was inside the button. I thought this is what "cancelsTouchesInView" is for, but that doesn't work.

My question now is: How to tell my buttons to ignore taps when a gesture is recognized?

Edit: This is my gesture recognizer.

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapped:)];
[doubleTap setNumberOfTouchesRequired:2];
[doubleTap setNumberOfTapsRequired:1];
[doubleTap setCancelsTouchesInView:YES];
[doubleTap setDelaysTouchesBegan:YES];
[doubleTap setDelaysTouchesEnded:YES];
[self.view addGestureRecognizer:doubleTap];
[doubleTap release];

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

以歌曲疗慰 2024-11-18 18:16:02

据苹果开发人员称,这是一个错误。我向 Apple 提交了错误报告。
非常感谢您的提示,Deepak 和 gcamp!

错误报告:

摘要:
将两个 UITapGestureRecognizer 添加到视图时,其中一个要求另一个失败 (requiresGestureRecognizerToFail:),第一个手势识别器的 cancelsTouchesInView 属性将被忽略。

重现步骤:
1.创建两个UITapGestureRecognizer(r1和r2)
2. 将 r1 配置为需要两次触摸和一次点击,并延迟触摸开始
3. 将 r2 配置为需要两次触摸和两次点击,并延迟 TouchBegan
4.配置r1要求r2失败[r1 requestsGestureRecognizerToFail:r2]
5. 将r1和r2添加到视图中
6. 在视图中放置一个UIButton
7. 用两根手指点击视图,其中一根手指应该点击按钮。

预期结果:
r1 应该被识别并且按钮点击应该被取消(对于 UITapGestureRecognizers,cancelsTouchesInView 默认为 YES)。

实际结果:
r1 被识别,但按钮 TouchUpInside 事件也被触发。

回归:
一旦您删除了对 r2 的依赖(第 4 步),cancelTouchesInView 就可以很好地用于 r1。

According to an Apple dev this is a bug. I filed a bug report with Apple.
Thanks a lot for your hints, Deepak and gcamp!

Bug report:

Summary:
When adding two UITapGestureRecognizers to a view where one requires the other to fail (requiresGestureRecognizerToFail:) the cancelsTouchesInView property of the first gesture recognizer is ignored.

Steps to Reproduce:
1. Create two UITapGestureRecognizers (r1 and r2)
2. Configure r1 to require two touches and one tap and to delay touchesBegan
3. Configure r2 to require two touches and two taps and to delay touchesBegan
4. Configure r1 to require r2 to fail [r1 requiresGestureRecognizerToFail:r2]
5. Add r1 and r2 to a view
6. Place a UIButton in the view
7. Tap with two fingers on the view, one should hit the button.

Expected Results:
r1 should be recognized and the button tap should be canceled (cancelsTouchesInView defaults to YES for UITapGestureRecognizers).

Actual Results:
r1 is recognized but the button touchedUpInside event is fired, too.

Regression:
cancelTouchesInView works fine for r1 once you remove the dependency on r2 (step 4).

三人与歌 2024-11-18 18:16:02

UIGestureRecognizer 上有一种方法可以回答您的问题

- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer

基本上,您要求两次点击识别器在接受单击识别器之前失败。

所以,

[singleTapRecognizer requireGestureRecognizerToFail:twoTapRecognizer];

There's a method on UIGestureRecognizer that respond to your question

- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer

Basically you're requiring that the two tap recognizer fail before accepting the single tap one.

So,

[singleTapRecognizer requireGestureRecognizerToFail:twoTapRecognizer];
淡墨 2024-11-18 18:16:02

使用delaysTouchesBegan 属性。将其设置为YES

替代

禁用按钮上的用户交互,并像前面提到的那样附加单指点击识别器。在点击处理程序中,检查点击是否落在按钮的范围内。如果它在按钮的范围内,请执行 [theButton sendActionsForControlEvents:UIControlEventTouchUpInside];。即使用户交互被禁用,这也会根据需要触发触摸事件。

Use the delaysTouchesBegan property. Set it to YES.

Alternative

Disable user interaction on the buttons and attach a single finger tap recognizer like mentioned. In the tap handler, check if the tap falls within the bounds of the button. If it is within the bounds of a button, do [theButton sendActionsForControlEvents:UIControlEventTouchUpInside];. This will trigger the touch up event as desired even though the user interaction is disabled.

轻许诺言 2024-11-18 18:16:02

马克,我遇到了同样的错误。如果你发布你的雷达#,我会在 bugreporter 中欺骗它。

我写了以下解决方法。我对 UITapGestureRecognizer 进行子类化,然后使用该子类来跟踪触发手势操作的触摸。如果我们在视图中的 TouchesEnded 中获得相同的触摸,我们将重定向到 TouchesCancelled。需要“immediateTarget”,因为视图上的touchesEnded 调用发生在手势的touchesEnded 之后但在调用手势的操作之前。

RPTapGestureRecognizer.h:

#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>

@interface RPTapGestureRecognizer : UITapGestureRecognizer

@property (nonatomic, retain) NSSet *lastTouches;

- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction;

@end

RPTapGestureRecognizer.m:

#import "RPTapGestureRecognizer.h"

@interface RPTapGestureRecognizer ()
@property (nonatomic) SEL immediateAction;
@property (nonatomic, assign) id immediateTarget;
@end

@implementation RPTapGestureRecognizer

@synthesize lastTouches;
@synthesize immediateAction, immediateTarget;

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.lastTouches = nil;

    [super touchesBegan:touches withEvent:event];
}

- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction
{
    self.immediateTarget = inTarget;
    self.immediateAction = inAction;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UIGestureRecognizerState startingState, finalState;

    startingState = self.state;
    [super touchesEnded:touches withEvent:event];
    finalState = self.state;

    if (startingState != finalState &&
        finalState == UIGestureRecognizerStateEnded) {
        /* Must copy; the underlying NSCFSet will be modified by the superclass, removing the touches */
        self.lastTouches = [[touches copy] autorelease];

        if (self.immediateAction)
            [self.immediateTarget performSelector:self.immediateAction
                                       withObject:self];
    }
}

- (void)dealloc
{
    self.lastTouches = nil;
    self.immediateTarget = nil;

    [super dealloc];
}

@end

视图中:

@synthesize lastTapGesture

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (self.lastTapGesture && [self.lastTapGesture.lastTouches isEqualToSet:touches]) {
        [self touchesCancelled:touches withEvent:event];
        self.lastTapGesture = nil;
        return;
    }
 /* touches ended implementation here */
}

 - (void)willHandleMouseTapGesture:(RPTapGestureRecognizer *)tapGesture
{
    /* Because one tap gesture is dependent upon another, the touchesEnded method is still going to be called, instead of touchesCanceled.
     * This is an Apple bug against all versions of iOS at least up to 5.0. It will be called in the run loop before tapGesture's action is called.
     *
     * See http://stackoverflow.com/questions/6188997/how-to-cancel-button-tap-if-uigesturerecognizer-fires/6211922#6211922 for details.
     */
    self.lastTapGesture = tapGesture;
}

- (void)configureGestures
{
    /* Two finger taps */
     RPTapGestureRecognizer *tapGestureOne;
     tapGestureOne = [[[RPTapGestureRecognizer alloc] initWithTarget:self 
                                                              action:@selector(handleMouseTapGesture:)] autorelease];
     tapGestureOne.numberOfTapsRequired = 1;
     tapGestureOne.numberOfTouchesRequired = 2;
     [tapGestureOne setImmediateTarget:self action:@selector(willHandleMouseTapGesture:)];
     [self addGestureRecognizer:tapGestureOne];
     [myGestures addObject:tapGestureOne];

     RPTapGestureRecognizer *tapGestureTwo;
     tapGestureTwo = [[[RPTapGestureRecognizer alloc] initWithTarget:self 
                                                              action:@selector(handleMouseTapGesture:)] autorelease];
     tapGestureTwo.numberOfTapsRequired = 2;
     tapGestureTwo.numberOfTouchesRequired = 2;
     [tapGestureTwo setImmediateTarget:self action:@selector(willHandleMouseTapGesture:)];
     [self addGestureRecognizer:tapGestureTwo];
}

Mark, I ran into the same bug. If you'll post your radar #, I'll dupe it in bugreporter.

I wrote the following workaround. I subclass UITapGestureRecognizer, then use the subclass to track the touches which triggered the gesture action. If we get the same touches in touchesEnded on the view, we redirect to touchesCancelled. 'immediateTarget' is needed because the touchesEnded call on the view comes after the gesture's touchesEnded but before the gesture's action is called.

RPTapGestureRecognizer.h:

#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>

@interface RPTapGestureRecognizer : UITapGestureRecognizer

@property (nonatomic, retain) NSSet *lastTouches;

- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction;

@end

RPTapGestureRecognizer.m:

#import "RPTapGestureRecognizer.h"

@interface RPTapGestureRecognizer ()
@property (nonatomic) SEL immediateAction;
@property (nonatomic, assign) id immediateTarget;
@end

@implementation RPTapGestureRecognizer

@synthesize lastTouches;
@synthesize immediateAction, immediateTarget;

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.lastTouches = nil;

    [super touchesBegan:touches withEvent:event];
}

- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction
{
    self.immediateTarget = inTarget;
    self.immediateAction = inAction;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UIGestureRecognizerState startingState, finalState;

    startingState = self.state;
    [super touchesEnded:touches withEvent:event];
    finalState = self.state;

    if (startingState != finalState &&
        finalState == UIGestureRecognizerStateEnded) {
        /* Must copy; the underlying NSCFSet will be modified by the superclass, removing the touches */
        self.lastTouches = [[touches copy] autorelease];

        if (self.immediateAction)
            [self.immediateTarget performSelector:self.immediateAction
                                       withObject:self];
    }
}

- (void)dealloc
{
    self.lastTouches = nil;
    self.immediateTarget = nil;

    [super dealloc];
}

@end

In the view:

@synthesize lastTapGesture

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (self.lastTapGesture && [self.lastTapGesture.lastTouches isEqualToSet:touches]) {
        [self touchesCancelled:touches withEvent:event];
        self.lastTapGesture = nil;
        return;
    }
 /* touches ended implementation here */
}

 - (void)willHandleMouseTapGesture:(RPTapGestureRecognizer *)tapGesture
{
    /* Because one tap gesture is dependent upon another, the touchesEnded method is still going to be called, instead of touchesCanceled.
     * This is an Apple bug against all versions of iOS at least up to 5.0. It will be called in the run loop before tapGesture's action is called.
     *
     * See http://stackoverflow.com/questions/6188997/how-to-cancel-button-tap-if-uigesturerecognizer-fires/6211922#6211922 for details.
     */
    self.lastTapGesture = tapGesture;
}

- (void)configureGestures
{
    /* Two finger taps */
     RPTapGestureRecognizer *tapGestureOne;
     tapGestureOne = [[[RPTapGestureRecognizer alloc] initWithTarget:self 
                                                              action:@selector(handleMouseTapGesture:)] autorelease];
     tapGestureOne.numberOfTapsRequired = 1;
     tapGestureOne.numberOfTouchesRequired = 2;
     [tapGestureOne setImmediateTarget:self action:@selector(willHandleMouseTapGesture:)];
     [self addGestureRecognizer:tapGestureOne];
     [myGestures addObject:tapGestureOne];

     RPTapGestureRecognizer *tapGestureTwo;
     tapGestureTwo = [[[RPTapGestureRecognizer alloc] initWithTarget:self 
                                                              action:@selector(handleMouseTapGesture:)] autorelease];
     tapGestureTwo.numberOfTapsRequired = 2;
     tapGestureTwo.numberOfTouchesRequired = 2;
     [tapGestureTwo setImmediateTarget:self action:@selector(willHandleMouseTapGesture:)];
     [self addGestureRecognizer:tapGestureTwo];
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文