具有UITapGestureRecognizer的视图内的UIButton


184

我有一个观点UITapGestureRecognizer。因此,当我点击视图时,该视图上方会出现另一个视图。此新视图具有三个按钮。现在,当我按下这些按钮之一时,我没有得到按钮动作,只有击打手势动作。因此,我无法再使用这些按钮。我该怎么做才能使事件通过这些按钮?奇怪的是按钮仍然突出显示。

收到UITapGestureRecognizer的提示后,我不能只删除它。因为有了它,新视图也可以被删除。意味着我想要像全屏视频控件那样的行为

Answers:


256

您可以将控制器或视图(以创建手势识别器的任何视图)设置为的委托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,并在.mi中添加了您的代码。水龙头从不输入此方法,它直接进入我的处理程序。有任何想法吗?
kmehta 2011年

1
@kmehta,您很可能忘记设置UIGestureRecognizer委托属性。
直到

可以将此答案概括为处理涉及IBAction的所有情况吗?
Martin Wickman

@Martin IBAction会向下编译,void因此在运行时不会留下任何信息用于检测。但是您可以做的是沿视图层次结构前进,测试UIControlNO如果找到任何控件,则返回。
莉莉·巴拉德

谢谢,这对我有帮助。如果按钮在UIImageView中,请确保imageView的userInteractionEnabled是否设置为YES。
james075

156

作为Casey对Kevin Ballard的回答的跟进:

- (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

Swift 4+解决方案:返回!(touch.view是UIControl)
nteissler,

103

在这里找到此答案:链接

您也可以使用

tapRecognizer.cancelsTouchesInView = NO;

这阻止了抽头识别器成为捕获所有抽头的唯一方法

UPDATE - 迈克尔提到的链接描述这个属性的文档:cancelsTouchesInView


8
这应该是IMO接受的答案。这样,每个子视图都可以自己处理水龙头,并防止连接了Tabrecognizer的视图“劫持”水龙头。如果您要做的就是使视图可单击(不需要急救人员/在文本字段上隐藏键盘等),则无需实现委托。
EeKay 2012年

4
这绝对应该是公认的答案。这个答案使我正确阅读了Apple文档,这很清楚,除非您这样做,否则手势识别器将阻止子视图获取已识别的事件。
Michael van der Westhuizen 2012年

1
这曾经有用,但现在对我不起作用..也许是因为按钮下面有一个scrollView吗?我必须像上面的答案一样实现委托。
xissburg,2012年

7
经过一番试验后,我注意到只有在包含手势识别器的视图是UIControl的同级视图时才有效,而如果视图是UIControl的父级则不会起作用。
xissburg,2012年

6
确实无法按预期工作...是的,它阻止了水龙头识别器在UIControl上吃掉该事件。但它仍在运行识别器动作,该动作同时运行2个动作。
Tek Yin

71

作为凯文·巴拉德(Kevin Ballard)回答的后续,我遇到了同样的问题,并最终使用了以下代码:

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

它具有相同的效果,但是可以在任何视图深度的任何UIButton上使用(我的UIButton有几个视图深度,并且UIGestureRecognizer的委托没有对此的引用。)


6
不想在这里成为鸡巴,但这真的是,不是。请使用可可惯例。TRUE / FALSE / NULL用于CoreFoundation层。
steipete 2011年

根据我所读的内容,实际上是为了提高可读性而创建的是和否。可读性是主观的。它源于并非所有人都过分关注的方法和属性命名约定。如果要严格遵守命名约定,则对任何NSWhat使用YES和NO。但是,我要说的是,如果您对开发人员进行调查,您将对以下哪一种更易读而有不同意见[button setHidden:YES];-或-[button setHidden:TRUE];
皮条客果汁McJones 2011年

1
我想我要说的是,如果命名约定的前提是可读性,那么我认为很难否认它在某些情况下是主观的。如果您是一个纯粹主义者,那么您将不会理解该声明。
皮条客果汁McJones 2011年

2
看起来似乎是主观的,但是经过一段时间的Cocoa编程之后,您真正进入了语义上更准确的代码流……在多年的Objective-C之后,“ TRUE”现在似乎真的错了,因为它与“隐藏”非常不匹配好。
Kendall Helmstetter Gelner 2012年

从什么时候开始/何时可以将轻击手势作为UITouch事件内部的Touch Up传递给UIButton(后者是通过轻击UIButton生成的事件)?为具有15个已与轻击手势相关联的触摸事件的按钮创建轻击手势识别器似乎是重复且不正确的。我想看看代码,而不是猜测。
詹姆斯·布什

10

在iOS 6.0及更高版本中,默认控制操作可防止手势识别器行为重叠。例如,按钮的默认操作是单击。如果您在按钮的父视图上附加了一个点击手势识别器,并且用户点击了该按钮,则按钮的操作方法将接收触摸事件而不是手势识别器。这仅适用于与控件的默认操作重叠的手势识别,该控件包括:.....

来自Apple的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:方法是唯一对我有用的方法。
肯尼·怀兰德2014年

3

这是对我有用的 Swift版本的Lily Ballard的答案

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

3

斯威夫特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)
}

1

您可以通过设置以下布尔值来阻止UITapGestureRecognizer取消其他事件(例如,点击按钮):

    [tapRecognizer setCancelsTouchesInView:NO];

1

如果您的情况是这样的:

您有一个简单的视图,并添加了一些UIButtons,UITextField控件作为该视图的子视图。现在,当您触摸视图上除控件(添加的子视图)以外的其他任何位置时,都想关闭键盘

那么解决方案是:

将以下方法添加到XYZViewController.m(具有视图)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.view endEditing:YES];
}

首先,我尝试使用@cdasher的答案,但就我而言,即使单击按钮,我也将touch.view作为ViewController中的“视图”而不是我的“ UIButton”控件。有人可以说一下为什么touch的view属性没有返回发生点击的原始视图
吗?

1
如果您有滚动视图或其他一些声称触摸事件的视图,则该功能将不起作用。
lkraider

1

优化cdasher的答案,您将获得

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch 
{
    return ![touch.view isKindOfClass:[UIControl class]];
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.