【问题标题】:Passing through touches to UIViews underneath通过触摸传递到下面的 UIViews
【发布时间】:2012-02-20 00:25:03
【问题描述】:

我有一个UIView,上面有4 个按钮,另一个UIView 在按钮视图的顶部。最上面的视图包含一个UIImageView,上面有一个UITapGestureRecognizer

我试图创建的行为是,当用户点击UIImageView 时,它会在屏幕右下角变小和动画变大之间切换。当它很大时,我希望禁用底部视图上的按钮,当它很小并且在右下角时,我希望将触摸传递给按钮并使它们正常工作。我快到了,但除非我禁用顶视图的UserInteractions,否则我无法让触摸传递到按钮。

我在顶部视图的initWithFrame: 中有这个:

// Add a gesture recognizer to the image view
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imageTapped:)];
tapGestureRecognizer.cancelsTouchesInView = NO;
[imageView addGestureRecognizer:tapGestureRecognizer];
[tapGestureRecognizer release];

这是我的imageTapped: 方法:

- (void) imageTapped:(UITapGestureRecognizer *) gestureRecognizer {
    // Toggle between expanding and contracting the image
    if (expanded) {
        [self contractAnimated:YES];
        expanded = NO;
        gestureRecognizer.cancelsTouchesInView = NO;
        self.userInteractionEnabled = NO;
        self.exclusiveTouch = NO;
    }
    else {
        [self expandAnimated:YES];
        expanded = YES;
        gestureRecognizer.cancelsTouchesInView = NO;
        self.userInteractionEnabled = YES;
        self.exclusiveTouch = YES;
    }
}

使用上面的代码,当图像很大时按钮处于非活动状态,当我触摸图像时它会缩小并且按钮变为活动状态。但是,小图像没有接收到触摸,因此不会展开。

如果我在这两种情况下都设置了self.userInteractionEnabled = YES,那么图像在被触摸时会展开和收缩,但按钮永远不会被触摸并且就像被禁用一样。

是否可以让图像在触摸时展开和收缩,但下方的按钮仅在图像处于收缩状态时才接收触摸?我是不是在这里做了一些愚蠢的事情而遗漏了一些明显的东西?

我要让这个工作变得非常疯狂,所以任何帮助都将不胜感激,

戴夫

更新:

为了进一步测试,我覆盖了 touchesBegan:touchesCancelled: 方法,并在包含 UIImageView 的视图上调用了它们的超级实现。使用上面的代码,touchesCancelled: 永远不会被调用,而touchesBegan: 总是会被调用。

所以看起来视图正在获得触摸,它们只是没有传递到下面的视图。

更新

这是因为响应者链的工作方式吗?我的视图层次结构如下所示:

VC - View1
     -View2
      -imageView1 (has tapGestureRecogniser)
      -imageView2
     -View3
      -button1
      -button2

我认为操作系统首先执行 hitTest,因为 View2 在前面,所以应该得到所有的触摸,并且这些触摸永远不会传递给 View3,除非 View2 的 userInteractions 设置为 NO,在这种情况下,imageView1 也被阻止接收触及。这是它的工作原理吗?有没有办法让 View2 通过它对 View3 的接触?

【问题讨论】:

  • 你是如何扩展和收缩你的 imageView 的?编辑:我假设你仍然用你的 imageView 以某种方式覆盖按钮,即使它在视觉上是收缩的。
  • UIImageView 是另一个视图的子视图。正是这个视图覆盖了按钮,当您在我的代码中看到 self 时,它是 UIView 的子类,并将 UIImageView 添加为子类。希望这是有道理的。
  • 请参阅 khanlou.com/2018/09/hacking-hit-tests 以获得对此的良好讨论。

标签: iphone ios uiview uiimageview uitapgesturerecognizer


【解决方案1】:

我认为 UIGestureRecognizer 是一个红鲱鱼。最后为了解决这个问题,我覆盖了我的UIView:pointInside:withEvent: 方法

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    BOOL pointInside = NO;

    if (CGRectContainsPoint(imageView.frame, point) || expanded) pointInside = YES;

    return pointInside;
}

如果您触摸 imageView 或设置了扩展标志,这会导致视图捕获所有触摸。如果它没有展开,那么只有当它们在 imageView 上时才捕获触摸。

通过返回 NO,顶级 VC 的视图查询其视图层次结构的其余部分以寻找命中。

【讨论】:

  • 感谢您提供此答案中的信息!然而,我最终覆盖了hitTest 而不是pointInside。这是因为我试图确保 parent 视图没有窃取任何水龙头,但希望所有子视图都能够。通过覆盖hitTest,您可以简单地调用基本实现来确定哪个视图被点击,如果该视图恰好是self,则返回nil
  • 以下解决方案更好。直接从情节提要中取消选中“启用用户交互”
【解决方案2】:

StoryboardXIB 中选择您的View 和...



或者在 Swift 中

view.isUserInteractionEnabled = false

【讨论】:

  • 这也会禁用对视图子视图的触摸..如果有的话
  • 如果您有来自同一个超级视图的 2 个视图,这将非常有用,谢谢!
【解决方案3】:

查看UIGestureRecognizerDelegate Protocol。具体来说,gestureRecognizer:shouldReceiveTouch:

您需要将每个 UIGestureRecognizer 设为您的 UIViewController 的属性,

// .h
@property (nonatomic, strong) UITapGestureRecognizer *lowerTap;

// .m
@synthesize lowerTap;

// When you are adding the gesture recognizer to the image view
self.lowerTap = tapGestureRecognizer

确保将您的 UIViewController 设为代表,

[self.lowerTap setDelegate: self];

那么,你会有这样的东西,

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if (expanded && gestureRecognizer == self.lowerTap) {    
        return NO;
    }
    else {
        return YES;
    }
}

当然,这不是确切的代码。但这是您想要遵循的一般模式。

【讨论】:

  • 您好,johnlyjohn,查看了委托协议,我看不出这对我有什么作用。我希望手势识别器始终接收触摸,我只是有时希望能够将它们传递给窗口中的其他视图,而不是取消它们。根据文档 cancelsTouchesInView = NO 应该通过它们,只要父视图也将 userInteractionEnabled 设置为 NO,它们就会这样做。
  • 你想传递什么样的触摸?例如,您可以调用其他视图无论如何都会调用的方法,而不是简单地在else 子句中返回YES
  • 啊,我知道你要去哪里了。恐怕我真的做不到。将我的视图子类视为控件。我知道它的状态并且可以根据用户输入来改变它,但它不知道它被放置在什么上面,或者任何将被调用的方法。这有意义吗?
  • 这通常是 100% 正确的答案。只需使用 shouldReceiveTouch 来传递点击。好的,约翰
  • @awfullyjohn,如果您需要最上面的UIView 来传递事件怎么办?你不能创建一个子类。
【解决方案4】:

我有另一个解决方案。我有两个视图,我们称它们为CustomSubView,它们是重叠的,它们都应该得到触摸。所以我有一个视图控制器和一个自定义 UIView 类,我们将其称为我在界面生成器中设置的 ViewControllerView,然后我添加了两个应该接收该视图的触摸的视图。

所以我通过覆盖hitTest来拦截ViewControllerView中的触摸:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    return self;
}

然后我改写了ViewControllerView

- (void)touchesBegan:(NSSet *)touches
           withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    for (UIView *subview in [self.subviews reverseObjectEnumerator])
    {
        if ([subview isKindOfClass:[CustomSubView class]])
        {
            [subview touchesBegan:touches withEvent:event];
        }
    }
}

touchesMoved touchesEndedtouchesCancelled 执行完全相同的操作。

【讨论】:

    【解决方案5】:

    @Magic Bullet Dave 的解决方案,但在 Swift 中

    斯威夫特 3

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var pointInside = false
        if commentTextField.frame.contains(point) {
            pointInside = true
        } else {
            commentTextField.resignFirstResponder()
        }
        return pointInside
    }
    

    我在我的 CameraOverlayView 中为 ImagePickerViewController.cameraOverlay 使用它,让用户能够在拍摄新照片时发表评论

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-10-04
      • 2014-02-13
      • 2014-01-12
      • 2013-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多