超越UIView的交互


92

当UIButton的框架位于其父框架的外部时,UIButton(或与此相关的任何其他控件)是否有可能接收触摸事件?原因是当我尝试此操作时,我的UIButton似乎无法接收任何事件。我该如何解决?


1
这对我来说非常有效。developer.apple.com/library/ios/qa/qa2013/qa1812.html最佳
Frank Li

2
这也很好并且相关(或可能是欺骗):stackoverflow.com/a/31420820/8047
Dan Rosenstark

Answers:


93

是。您可以重写此hitTest:withEvent:方法以返回一个视图,该视图具有比该视图包含的更大的点集。请参阅《UIView类参考》

编辑:示例:

- (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];
    }

请注意,您很可能self.oversizeButton在此UIView子类中将它作为IBOutlet来使用;那么您只需将相关的“特大按钮”拖到相关的特殊视图即可。(或者,由于某种原因,如果您在项目中经常执行此操作,则将有一个特殊的UIButton子类,并且可以在子视图列表中查看这些类。)希望对您有所帮助。


您可以通过一些示例代码来帮助我进一步正确地实现这一点吗?我也阅读了该参考书,并且由于以下几行,我感到困惑:“即使位于接收者边界之外的点实际上位于接收者的子视图之一内,也永远不会将其报告为匹配项。子视图可能在视觉上超出边界如果将父视图的clipsToBounds属性设置为NO,则其父视图的值不变。但是,命中测试始终忽略父视图边界之外的点。”
ThomasM 2011年

尤其是这部分使它看起来似乎不是我需要的解决方案:“但是,命中测试始终会忽略父视图范围之外的点。”但是我当然可能是错的,我发现苹果引用有时确实令人困惑..
ThomasM 2011年

啊,我现在明白了。您需要覆盖父项,pointInside:withEvent:以包括按钮的框架。我将在上面的答案中添加一些示例代码。
jnic 2011年

有没有一种方法可以覆盖UIView的方法?例如,如果您的视图完全是UIKit通用的并通过Nib初始化,则可以从UIViewController中重写这些方法吗?
RLH 2012年

1
hitTest:withEvent:pointInside:withEvent:当我按下位于父母范围之外的按钮时,并没有呼叫我的区域。有什么事吗
iosdude

24

@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,所有子元素也是UIButton,可以移到父UIButton的边界之外。

最好


20

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

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

}

在这种情况下,如果该点落在按钮的范围内,则将呼叫转接到该按钮;如果不是,请还原为原始实现。


17

就我而言,我有一个UICollectionViewCell包含的子类UIButton。我禁用clipsToBounds了该单元格,并且该按钮在单元格边界之外可见。但是,该按钮未收到触摸事件。通过使用杰克的答案,我能够检测到按钮上的触摸事件:https : //stackoverflow.com/a/30431157/3344977

这是Swift版本:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    let translatedPoint = button.convertPoint(point, fromView: self)

    if (CGRectContainsPoint(button.bounds, translatedPoint)) {
        print("Your button was pressed")
        return button.hitTest(translatedPoint, withEvent: event)
    }
    return super.hitTest(point, withEvent: event)
}

斯威夫特4:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

    let translatedPoint = button.convert(point, from: self)

    if (button.bounds.contains(translatedPoint)) {
        print("Your button was pressed")
        return button.hitTest(translatedPoint, with: event)
    }
    return super.hitTest(point, with: event)
}

那正是我的情况。另一点是,您应该将此方法放入CollectionViewCell类中。
Hamid Reza Ansari,

但是为什么要将按钮注入到重写的方法中呢???
rommex

10

为什么会这样呢?

这是因为,当您的子视图超出超级视图的范围时,该子视图上实际发生的触摸事件将不会传递到该子视图。然而,WILL交付给它的父。

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

你需要做什么?

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

那代码呢?

在您的监督下,实施 func hitTest(_ point: CGPoint, with event: UIEvent?)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isHidden || alpha == 0 || clipsToBounds { return super.hitTest(point, with: event) }
        // 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 super.hitTest(point, with: event)
    }

令人着迷的哥特奇:您必须去“最高的太小的超级观看”

您必须“向上”进入问题视图位于外部的“最高”视图。

典型示例:

假设您有一个带有容器视图C的屏幕S。容器视图控制器的视图为V。实际在V之外的视图

但请注意,B也在C之外。

在这个例子中,你必须应用的解决方案override hitTest其实到C,而不是到V。如果将其应用于V,则不会执行任何操作。


4
这次真是万分感谢!我在这个问题上花了几个小时。非常感谢您抽出宝贵时间发布此信息。:)
ClayJ

@ScottZhu:我确实在您的答案中添加了一个重要的gotchya。当然,您应该随意删除或更改我的添加!再次感谢!
Fattie

9

迅速:

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)
}

5

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

Superview 这是一种观点,即如果将其clipsToBounds设置为true,它将使整个问题消失。这是我UIView在Swift 3中的简单操作:

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];
    }
}

这个答案(和其他几个关于同一问题的答案)可以帮助您理解。


2

迅捷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)
}
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.