UIGestureRecognizer阻止子视图处理触摸事件

85

我正在尝试找出如何以正确的方式完成这个任务。我试图描述一下情况: enter image description here

我正在将一个UITableView作为UIView的子视图添加进去。当UIView响应点击和pinchGestureRecognizer时,但这样做时,tableview不再响应这两个手势(仍然响应滑动手势)。

我已经用了以下代码使其正常工作,但显然这不是一个好的解决方案,我相信有更好的方法。这段代码放在UIView中(父视图):

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if([super hitTest:point withEvent:event] == self) {
        for (id gesture in self.gestureRecognizers) {
            [gesture setEnabled:YES];
        }
        return self;
    }
    for (id gesture in self.gestureRecognizers) {
        [gesture setEnabled:NO];
    }
    return [self.subviews lastObject];
}
10个回答

184

我曾遇到类似的问题,并在这个SO问题中找到了解决方案。简而言之,将自己设置为UIGestureRecognizer的委托,然后在允许识别器处理触摸前检查目标视图。相关的委托方法是:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch

3
我最喜欢这种解决方案,因为它不涉及触摸、hitTest:withEvent:pointInside:withEvent: 的操作。 - DarkDust
7
干净的解决方案,例如可以使用return !(touch.view == givenView);来测试,如果您只想排除给定的视图,或者当您想要停止识别器处理各种不同子视图上的触摸时,可以使用return !(touch.view.tag == kTagNumReservedForExcludingViews); - cate
6
我会使用- (BOOL)isDescendantOfView:(UIView *)view进行点击测试。在子类化UIGestureRecognizer时,在- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event中也可以正常工作。 - Christoph
1
@Justin,如果我们的手势识别器属于UIScrollView并且我们无法委托手势识别器怎么办? - Gökhan Barış Aker

111

阻止触摸事件传递到子视图是默认行为。您可以更改此行为:

UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];

5
这将会把触摸事件传递给子视图,同时也会传递给手势识别器。这样可以避免阻塞子视图,但手势识别器仍然可以在子视图上被识别到。 - Jonathan.
@Jonathan。是的,我同意你的观点。在我的手势处理方法中,我检查手势位置是否发生在相关的子视图中,如果是,则无需执行其余的代码。此外,我选择这种解决方法的原因之一是UITapGestureRecognizer没有声明translationInView方法。因此,实现上述UIGestureRecognizerDelegate方法只会导致崩溃错误... unrecognized selector sent to blah。要检查,请使用类似于:CGRectContainsPoint(subview.bounds, [recognizer locationInView:subview])的东西。 - MkVal
根据提问者的问题,应该选择这个答案作为主要答案。 - farzadshbfn

5

在 @Pin Shih Wang 的回答 基础上,我们忽略除了手势识别器所在视图上的点击事件之外的所有点击。由于我们设置了tapGestureRecognizer.cancelsTouchesInView = false,因此所有的点击事件都会像平常一样被转发到视图层次结构中。以下是Swift3/4中的代码:

func ensureBackgroundTapDismissesKeyboard() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    tapGestureRecognizer.cancelsTouchesInView = false
    self.view.addGestureRecognizer(tapGestureRecognizer)
}

@objc func handleTap(recognizer: UIGestureRecognizer) {
    let location = recognizer.location(in: self.view)
    let hitTestView = self.view.hitTest(location, with: UIEvent())
    if hitTestView?.gestureRecognizers?.contains(recognizer) == .some(true) {
        // I dismiss the keyboard on a tap on the scroll view
        // REPLACE with own logic
        self.view.endEditing(true)
    }
}

我该如何将手势识别器与 UISwipeActionStandardButton 进行比较?我尝试使用 .some(UISwipeActionStandardButton) - Jalil

5

我正在显示一个下拉子视图,它有自己的表格视图。因此,touch.view 有时会返回类似 UITableViewCell 的类。我必须逐级查看超类以确保它是我想要的子类:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    UIView *view = touch.view;
    while (view.class != UIView.class) {
        // Check if superclass is of type dropdown
        if (view.class == dropDown.class) { // dropDown is an ivar; replace with your own
            NSLog(@"Is of type dropdown; returning NO");
            return NO;
        } else {
            view = view.superview;
        }
    }

    return YES;
}

while循环考虑到了这一点。 - joslinm

4

一种可能的方法是对您的手势识别器进行子类化(如果您尚未这样做),并覆盖-touchesBegan:withEvent:,以便确定每个触摸是否始于被排除的子视图,并在确实如此时为该触摸调用-ignoreTouch:forEvent:

显然,您还需要添加一个属性来跟踪被排除的子视图,或者更好的方法是使用一个排除子视图的数组。



2

可以不继承任何类就能实现这个功能。

您可以在手势回调选择器中检查手势识别器。

如果视图的gestureRecognizers不包含您的gestureRecognizer,只需忽略它即可。

例如:

- (void)viewDidLoad
{
    UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self     action:@selector(handleSingleTap:)];
    singleTapGesture.numberOfTapsRequired = 1;
}

请在此处检查view.gestureRecognizers

- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer
{
    UIEvent *event = [[UIEvent alloc] init];
    CGPoint location = [gestureRecognizer locationInView:self.view];

    //check actually view you hit via hitTest
    UIView *view = [self.view hitTest:location withEvent:event];

    if ([view.gestureRecognizers containsObject:gestureRecognizer]) {
        //your UIView
        //do something
    }
    else {
        //your UITableView or some thing else...
        //ignore
    }
}

如其他答案所述,如果使用此方法,请确保轻拍手势识别器通过以下方式将轻拍转发到您的视图层次结构: 在 viewDidLoad 中添加 singleTapGesture.cancelsTouchesInView = NO; - Nick Ager

1
实现一个委托来处理parentView的所有识别器,并将gestureRecognizer方法放在负责同时触发识别器的委托中。
func gestureRecognizer(UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer:UIGestureRecognizer) -> Bool {
    if (otherGestureRecognizer.view.isDescendantOfView(gestureRecognizer.view)) {
        return true
    } else {
        return false
    }
}

如果您想让子识别器被触发而不是父识别器,可以使用失败方法:

https://developer.apple.com/reference/uikit/uigesturerecognizerdelegate


1
我创建了一个 UIGestureRecognizer 子类,旨在阻止特定视图的父视图附加的所有手势识别器。
这是我 WEPopover 项目的一部分。你可以在 这里 找到它。

0

我也在做一个弹出框,这是我的实现方式

func didTap(sender: UITapGestureRecognizer) {

    let tapLocation = sender.locationInView(tableView)

    if let _ = tableView.indexPathForRowAtPoint(tapLocation) {
        sender.cancelsTouchesInView = false
    }
    else {
        delegate?.menuDimissed()
    }
}

0

你可以把它关掉和打开......在我的代码中,我做了类似这样的事情,因为当键盘不显示时,我需要把它关掉,你可以应用到你的情况:

在viewdidload等方法中调用:

NSNotificationCenter    *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(notifyShowKeyboard:) name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:@selector(notifyHideKeyboard:) name:UIKeyboardWillHideNotification object:nil];

然后创建这两个方法:

-(void) notifyShowKeyboard:(NSNotification *)inNotification 
{
    tap.enabled=true;  // turn the gesture on
}

-(void) notifyHideKeyboard:(NSNotification *)inNotification 
{
    tap.enabled=false;  //turn the gesture off so it wont consume the touch event
}

这个操作是禁用了触摸功能。不过我必须将触摸转换为实例变量,并在dealloc中释放它。


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