一个带有UITapGestureRecognizer的视图中包含了一个UIButton

195

我有一个带有UITapGestureRecognizer的视图。因此,当我点击该视图时,另一个视图会出现在该视图上方。这个新视图有三个按钮。当我现在按下其中一个按钮时,我不能获得按钮的动作,我只能获得点击手势的动作。所以我无法再使用这些按钮。我该怎么做才能将事件传递给这些按钮呢?奇怪的是,这些按钮仍然会被突出显示。

我不能只是在接收到点击手势后删除UITapGestureRecognizer。因为带有它,新视图也可以被移除。我的意思是,我想要像全屏视频控件那样的行为。

12个回答

263

您可以将控制器或视图(无论哪一个创建了手势识别器)设置为UITapGestureRecognizer的代理。然后在代理中,您可以实现-gestureRecognizer:shouldReceiveTouch:。在您的实现中,您可以测试触摸是否属于您的新子视图,如果是,则指示手势识别器忽略它。类似以下内容:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    // test if our control subview is on-screen
    if (self.controlSubview.superview != nil) {
        if ([touch.view isDescendantOfView:self.controlSubview]) {
            // we touched our control surface
            return NO; // ignore the touch
        }
    }
    return YES; // handle the touch
}

在我的头文件中,我让我的视图实现了UIGestoreRegognizerDelegate,并在我的.m文件中添加了你的代码。但是点击从未进入这个方法,而是直接进入我的处理器。有什么想法吗? - kmehta
1
@kmehta,你很可能忘记设置UIGestureRecognizer委托属性了。 - Till
这个答案是否可以概括处理所有涉及IBAction的情况? - Martin Wickman
@Martin IBAction编译成void,因此在运行时不会留下任何信息用于检测。但是您可以沿着视图层次结构向上遍历,测试UIControl,如果找到任何控件,则返回NO - Lily Ballard
谢谢这个,它对我很有帮助。如果您的按钮在UIImageView中,请确保imageView的userInteractionEnabled设置为YES。 - james075

160
作为对凯西追加到凯文·巴拉德答案的跟进:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
        if ([touch.view isKindOfClass:[UIControl class]]) {
            // we touched a button, slider, or other UIControl
            return NO; // ignore the touch
        }
    return YES; // handle the touch
}

这基本上使所有用户输入类型的控件,如按钮、滑块等,都能够工作。


1
这是一个灵活的解决方案,可以搭配 setCancelsTouchesInView = NO 使用,以避免与控件交互时触发轻拍手势。不过你可以写得更好: return ![touch.view isKindOfClass:[UIControl class]]; - griga13
2
Swift 4+ 解决方案:返回值为 !(touch.view is UIControl) 的布尔类型。 - nteissler

108

在这里找到了答案:链接

你也可以使用

tapRecognizer.cancelsTouchesInView = NO;

这可以防止轻拍识别器成为唯一捕获所有轻拍的识别器。

更新 - Michael提到了文档中描述此属性的链接:cancelsTouchesInView


9
在我看来,这应该是被接受的答案。这样可以让每个子视图处理自己的轻拍操作,并防止附加有tabrecognizer的视图“劫持”轻拍。如果你想让一个视图可点击(用于放弃第一响应者/隐藏文本框上的键盘等),则无需实现代理..太棒了! - EeKay
4
这一定是被采纳的答案。这个答案让我好好阅读了 苹果官方文档, 明确了手势识别器会阻止子视图获取到被识别的事件,_除非_你这样做。 - Michael van der Westhuizen
1
这个以前可以用,但现在对我来说不起作用了...也许是因为我在按钮下面有一个scrollView?我必须像上面的答案中那样实现委托。 - xissburg
7
稍微尝试了一下,我发现只有当包含手势识别器的视图是UIControl的兄弟视图时才会起作用,如果该视图是UIControl的父视图,则不起作用。 - xissburg
这是最好的。谁知道默认情况下,触摸识别器会吃掉视图上的触摸?谁能猜到呢。 - n13
9
并非按照预期工作……是的,它可以防止 tap recognizer 吞噬 UIControl 上的事件。但仍会运行 recognizer 的动作,导致两个动作同时执行。 - Tek Yin

73

作为对Kevin Ballard答案的跟进,我遇到了相同的问题并最终使用了这段代码:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if ([touch.view isKindOfClass:[UIButton class]]){
        return NO;
    }
    return YES;
}

这具有相同的效果,但是这将适用于任何深度的视图中的任何UIButton(我的UIButton位于多个视图深度中,而UIGestureRecognizer的委托没有引用它。)


6
不想显得不友善,但实际上是“是”和“否”。请使用Cocoa约定。TRUE/FALSE/NULL是针对CoreFoundation-Layer的。 - steipete
根据我所了解的,YES和NO实际上是为了可读性而创建的。可读性是主观的,它源自于方法和属性命名约定,并不是每个人都特别关注的。如果您想要严格遵循命名约定,那么对于任何NSWhatever,都可以使用YES和NO。然而,我要说的是,如果您调查开发人员,您会得到关于哪个更可读的混合意见[button setHidden:YES]; -或者- [button setHidden:TRUE]; - Pimp Juice McJones
1
我想表达的是,如果命名约定的前提是可读性,那么我认为在某些情况下这是主观的,如果您是一个纯粹主义者,那么您可能不理解这个陈述。 - Pimp Juice McJones
2
在 Cocoa 编程一段时间后,你会发现更加注重代码的语义准确性,这可能有点主观。经过多年 Objective-C 的编程,"TRUE" 看起来就很不对了,因为它与 "hidden" 不太匹配。 - Kendall Helmstetter Gelner
你从什么时候开始/可以将轻拍手势传递给UIButton作为Touch Up Inside UITouch事件(后者是通过点击UIButton生成的事件)?为一个已经与15个触摸事件相关联的轻拍手势识别器创建一个按钮似乎是重复和不正确的。我想看到代码,而不是猜测。 - James Bush

11
在iOS 6.0及更高版本中,默认控件操作会防止重叠的手势识别器行为。例如,按钮的默认动作是单击。如果您将单击手势识别器附加到按钮的父视图上,并且用户点击了按钮,则按钮的操作方法会接收触摸事件,而不是手势识别器。这仅适用于与控件的默认操作重叠的手势识别,包括:......
参考自 苹果API文档。 (链接)

8

这些答案不完整。我不得不阅读多篇帖子,才能了解如何使用此布尔操作。

在您的 *.h 文件中添加以下内容:

@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>

在您的*.m文件中添加以下内容:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    NSLog(@"went here ...");

    if ([touch.view isKindOfClass:[UIControl class]])
    {
        // we touched a button, slider, or other UIControl
        return NO; // ignore the touch
    }
    return YES; // handle the touch
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.



    //tap gestrure
    UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
    [tapGestRecog setNumberOfTapsRequired:1];
    [self.view addGestureRecognizer:tapGestRecog];


// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;

}


-(IBAction) screenTappedOnce
{
    NSLog(@"screenTappedOnce ...");

}

7

我从这里找到了另一种方法。它检测每个按钮内部的触摸。

(1) pointInside:withEvent: 方法。 (2) locationInView: 方法。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch {
    // Don't recognize taps in the buttons
    return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
            ![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
            ![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}

我尝试了所有其他的解决方案,但是-pointInside:withEvent:方法是唯一适用于我的。 - Kenny Wyland

7

Swift 5

在父视图上使用按钮和轻拍手势

 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if let _ = touch.view as? UIButton { return false }
    return true
}

在我的情况下,实现hitTest对我有用。我有一个带有按钮的集合视图。
这种方法通过调用每个子视图的point(inside:with:)方法来遍历视图层次结构,以确定哪个子视图应该接收触摸事件。如果point(inside:with:)返回true,则类似地遍历子视图的层次结构,直到找到包含指定点的最前面的视图。
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled else { return nil }

    guard !isHidden else { return nil }

    guard alpha >= 0.01 else { return nil }

    guard self.point(inside: point, with: event) else { return nil }

    for eachImageCell in collectionView.visibleCells {
        for eachImageButton in eachImageCell.subviews {
            if let crossButton = eachImageButton as? UIButton {
                if crossButton.point(inside: convert(point, to: crossButton), with: event) {
                    return crossButton
                }
            }
        }
    }
    return super.hitTest(point, with: event)
}

3

这是我用过的Lily Ballard的答案的Swift版本:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if (scrollView.superview != nil) {
        if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
    }
    return true
}

1
优化cdasher的答案,你可以得到:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接