超越UIView界限的互动

时间:2011-03-25 13:21:28

标签: iphone objective-c cocoa uiview uibutton

当UIButton的帧位于其父级帧之外时,UIButton(或任何其他控件)是否可以接收触摸事件?因为当我尝试这个时,我的UIButton似乎无法接收任何事件。我该如何解决这个问题?

9 个答案:

答案 0 :(得分:90)

是。您可以覆盖hitTest:withEvent:方法以返回视图,以获得比该视图包含的更大的点集。请参阅UIView Class Reference

修改:示例:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(-radius, -radius,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}

编辑2:(澄清之后:)为了确保按钮被视为在父级边界内,您需要覆盖父级中的pointInside:withEvent:以包含按钮的帧。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if (CGRectContainsPoint(self.view.bounds, point) ||
        CGRectContainsPoint(button.view.frame, point))
    {
        return YES;
    }
    return NO;
}

注意正好用于覆盖pointInside的代码不太正确。正如Summon在下面解释的那样,这样做:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
    if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
        return YES;

    return [super pointInside:point withEvent:event];
    }

请注意,您很可能在此UIView子类中使用self.oversizeButton作为IBOutlet;然后你可以拖动有问题的特殊视图中的“超大尺寸按钮”。 (或者,如果由于某种原因你在一个项目中做了很多这样的事情,你就会有一个特殊的UIButton子类,你可以通过你的子视图列表查看这些类。)希望它有所帮助。

答案 1 :(得分:23)

@jnic,我正在开发iOS SDK 5.0,为了让你的代码正常工作,我必须这样做:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(button.frame, point)) {
    return YES;
}
return [super pointInside:point withEvent:event]; }

我的案例中的容器视图是一个UIButton,所有子元素也是UIButtons,可以移出父UIButton的边界。

最佳

答案 2 :(得分:19)

在父视图中,您可以覆盖命中测试方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGPoint translatedPoint = [_myButton convertPoint:point fromView:self];

    if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) {
        return [_myButton hitTest:translatedPoint withEvent:event];
    }
    return [super hitTest:point withEvent:event];

}

在这种情况下,如果该点落在按钮的范围内,则将呼叫转发到那里;如果没有,请恢复原始实施。

答案 3 :(得分:8)

夫特:

其中targetView是您希望接收事件的视图(可能完全或部分位于其父视图框架之外)。

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self)
        if CGRectContainsPoint(targetView.bounds, pointForTargetView) {
            return closeButton
        }
        return super.hitTest(point, withEvent: event)
}

答案 4 :(得分:7)

就我而言,我有一个包含UICollectionViewCell的{​​{1}}子类。我在单元格上禁用UIButton,按钮在单元格范围之外可见。但是,该按钮未接收触摸事件。我能够使用杰克的答案检测按钮上的触摸事件:https://stackoverflow.com/a/30431157/3344977

这是一个Swift版本:

clipsToBounds

答案 5 :(得分:5)

为什么会这样?

这是因为当您的子视图位于超级视图范围之外时,在该子视图上实际发生的触摸事件将不会传递到该子视图。但是,传递给其超级视图。

  

无论是否在视觉上剪切子视图,触摸事件始终遵循目标视图的超视图的边界矩形。换句话说,在超视图的边界矩形之外的视图的一部分中发生的触摸事件不会传递到该视图。 Link

您需要做什么?

当您的超级视图收到上述触摸事件时,您需要明确告诉UIKit我的子视图应该是接收此触摸事件的子视图。

代码怎么样?

在您的超级视图中,实施func hitTest(_ point: CGPoint, with event: UIEvent?)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isHidden || alpha == 0 || clipsToBounds { return nil }
        // convert the point into subview's coordinate system
        let subviewPoint = self.convert(point, to: subview)
        // if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event
        if ! subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
        return nil
    }

答案 6 :(得分:3)

对我(和其他人一样),答案是来覆盖hitTest方法,而是覆盖pointInside方法。我只能在两个地方做这件事。

超级视图 这是一种观点,即如果将clipsToBounds设置为true,则会使整个问题消失。所以这是我在Swift 3中的简单UIView

class PromiscuousView : UIView {
    override func point(inside point: CGPoint,
               with event: UIEvent?) -> Bool {
        return true
    }
} 

子视图 您的里程可能会有所不同,但在我的情况下,我还必须覆盖已确定触摸超出范围的子视图的pointInside方法。我的目标是Objective-C,所以:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    WEPopoverController *controller = (id) self.delegate;
    if (self.takeHitsOutside) {
        return YES;
    } else {
        return [super pointInside:point withEvent:event];
    }
}

This answer(以及其他一些关于同一问题的人)有助于澄清您的理解。

答案 7 :(得分:1)

swift 4.1更新

要在同一UIView中获取触摸事件,您只需添加以下重写方法,它将标识在UIView中轻按的特定视图,该视图被置于边界

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let pointforTargetview:CGPoint = self.convert(point, to: btnAngle)
    if  btnAngle.bounds.contains(pointforTargetview) {
        return  self
    }
    return super.hitTest(point, with: event)
}

答案 8 :(得分:1)

我知道这是一个很老的问题,但我们仍然需要这些解决方案作为更新版本,这就是为什么我会分享这个问题的新版本解决方案。

假设我的 mainView 框架是 CGRect(0.0, 0.0, 250, 250.0),我想在 subViewmainView(到左上角)添加一个宽度和高度为 50 的按钮,它的框架看起来像 CGRect(-50, -50, 50, 50) 中的 mainView 在这种情况下我们无法与此按钮交互,这里是如何解决此问题的示例

在您的 MainView(按钮容器视图) 中覆盖 hitTest 函数,如其他答案中所述,但此处反转子视图顺序并将命中点转换为该子视图点,然后仅使用 { 检查结果{1}} 再次 else 检查您的视图是否包含该点返回您的 hitTest else 返回 nil 以避免影响其他视图

mainView

我已经在自己的应用中测试并使用了这段代码,效果很好。