从UIGestureRecognizer中排除子视图

33

我有一个UIView(容器视图),其中包含多个“子视图”。我想向容器视图添加UITapGestureRecognizer,使其在我触摸容器视图内部但在子视图外部的区域时被激活。

目前,在容器视图内部的任何位置,包括子视图内部,都会激活手势识别器。

实现大致如下: 在控制器中:

ContainerView *containerView = [[ContainerView alloc] initWithSubViews:array];
UITapGestureRecognizer *tap = [UITapGestureRecognizer alloc] initWithTarget:self action:@selector(someSelector)];
[containerView addGestureRecognizer:tap];
[self.view addSubView:containerView];

在 ContainerView.m 文件中

-(id)initWithSubviews:(NSArray *)array {
    for (subView *s in array) {
        [self addSubView:s];
    }
    return self;
}

我认为问题的发生是因为手势识别器是在子视图之后添加的。如果这是真的,那么解决方案需要将initWithSubViews方法分成两个单独的方法,但我想避免这样做。

谢谢您

9个回答

42

我使用了以下简单的方法。它完美地���作用了!

实现UIGestureRecognizerDelegate函数,仅接受父视图上的触摸,不接受子视图上的触摸:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if (touch.view != _mySuperView) { // accept only touchs on superview, not accept touchs on subviews
        return NO;
    }

    return YES;
}

15

iOS 6推出了一个非常好的新功能,可以解决这个确切的问题 - UIView(子视图)可以从gestureRecognizerShouldBegin: (附加到父视图的手势识别器)返回NO。事实上,对于某些UIView子类关于一些手势识别器而言,这已经是默认值了(例如,UIButton关于附加到父视图的UITapGestureRecognizer)。

请查看我关于这个主题的书:http://www.apeth.com/iOSBook/ch18.html#_gesture_recognizers


13

我通过以下步骤使其正常工作:

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHandler:)];

// ...

-(void) tapGestureHandler:(UITapGestureRecognizer *)sender {
    CGPoint point = [sender locationInView:sender.view];
    UIView *viewTouched = [sender.view hitTest:point withEvent:nil];
    if ([viewTouched isKindOfClass:[ThingIDontWantTouched class]]) {
        // Do nothing;
    } else {
        // respond to touch action
    }
}

6

记得添加和设置委托方法 -

在您的UIViewController的.h文件中包含此委托。

@interface YourViewController: UIViewController<UIGestureRecogniserDelegate>

那么,当你创建tapGesture对象时(例如在viewDidLoad中),

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(actionMethodYouWantToHappenOnTap)];  

    tap.delegate = self;  //Remember to set delegate! Otherwise the delegate method won't get called.

    [self.view addGestureRecognizer:tap];

记得设置委托方法 tap.delegate = self;,然后 tap 的委托方法就会在点击时触发。

在这个方法中,你可以处理何时使用轻拍手势识别。在我的代码中,如果特定的子视图可见,我希望它忽略轻拍手势。

-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{

     if (!mySubview.hidden){
     return NO;   //This fired if said subview was visible, though whatever the condition of where and when you want tap to work, can be handled within this delegate method.
     }

    return YES;
}

希望这能帮助其他遇到相同问题的人。我发现设置委托方法是很容易被忽视的事情。

谢谢!


3

有很多答案,我再提供一个替代方案。将不想接收触摸的子视图的view tag初始化为1,并执行以下操作:

- (void)viewDidLoad {
    [super viewDidLoad];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    tap.delegate = self;
    [self.view addGestureRecognizer:tap];
}

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if (touch.view.tag == 1) {
        return NO;
    }
    return YES;
}

-(void)handleTap:(id)sender
{
//Handle tap...
}

使用这种方法,触摸手势仅在您需要的视图中接收。


1

已更新至Swift 5

@Awais Hussain的回答非常完美,

在手势视图中 -> 控制器视图 -> 子视图 -> 我不想被触摸的视图

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapGestureHandler(_:)))

    }


    @objc
    func tapGestureHandler(_ sender: UITapGestureRecognizer){
        let point = sender.location(in: sender.view)
        if let viewTouched = sender.view?.hitTest(point, with: nil), viewTouched is ViewThingI_DontWantToBeTouched{
            ()
            // Do nothing;
        }
        else {
            // respond to touch action
        }
    }
}

0

Swift 4 的更新答案

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if touch.view != yourView
        {
            return false
        }

        return true
    }

在此之前,请确保您已将委托属性设置为您的对象。


0
如果您为ContainerView添加了UITapGestureRecognizer,则它应该对整个ContainerView做出响应。它不会考虑其子视图。
检查手势位置,如果在您的子视图位置,则跳过手势操作。
- (void) tapGestureHandler:(UIGestureRecognizer *) gestureRecognizer {


    CGPoint tapPoint = [gestureRecognizer locationInView:nil]; 

   //check your tapPoint contains ur subview frame, skip.else do ur actions
}

如果需要检查很多子视图,这将变得非常昂贵和复杂。 - CodaFi
嗯,我明白了。我没有看到其他好的解决方案。如果你有的话,随时分享吧 :) - Shamsudheen TK

0

添加:

我突然想到了更好的方法。

在你的处理程序中

-(void)tapGestureHandler:(UITapGestureHandler)gesture

检查手势视图是否为父视图。 如果子视图被点击,它将返回子视图。

====================================================

我建议在父视图中进行重写。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

这被称为确定手势是否在视图内。

查看此问题的答案以了解其默认行为。

iOS事件处理 - 如何处理hitTest:withEvent:和pointInside:withEvent:?

您可以检查点是否在其任何子视图中。如果是,则返回nil,否则返回self。

或者

在每个子视图中添加一个不执行任何操作的tapgesture recognizer。子视图的手势识别器默认会取消其父视图的手势识别器。但是,如果有许多子视图,请注意内存占用。


很遗憾,我认为这个不起作用。根据文档,gesture.view会给你“手势识别器所附加的视图”,这将给出整个容器视图而不是子视图。第二个选项看起来很有前途,我会试一下。 - Awais Hussain
太好了,我通过使用hitTest:withEvent方法使其工作了。请参见下面的帖子。 - Awais Hussain

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