繁体   English   中英

超出 UIView 边界的交互

[英]Interaction beyond bounds of UIView

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

是的。 您可以覆盖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 子类,你可以查看这些类的子视图列表。)希望它有所帮助。

@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 的边界之外。

最好的事物

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

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

}

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

就我而言,我有一个包含UIButtonUICollectionViewCell子类。 我在单元格上禁用了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)
}

为什么会发生这种情况?

这是因为当您的子视图位于您的超级视图的边界之外时,该子视图上实际发生的触摸事件将不会传递到该子视图。 然而,交付给它的父。

不管子视图是否在视觉上被裁剪,触摸事件总是尊重目标视图的父视图的边界矩形。 换句话说,发生在位于其父视图边界矩形之外的视图部分中的触摸事件不会传递到该视图。 关联

你需要做什么?

当你的超级视图收到上面提到的触摸事件时,你需要明确地告诉 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)
    }

迷人的gotchya:你必须去“最高的太小的superview”

您必须“上升”到问题视图位于外部的“最高”视图。

典型例子:

假设您有一个屏幕 S,带有一个容器视图 C。容器视图控制器的视图是 V。(回想一下,V 将位于 C 内部并且大小相同。)V 有一个子视图(可能是一个按钮)B。B 是问题所在实际上在 V 之外的视图。

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

在此示例中,您必须将解决方案override hitTest实际上应用于 C 而不是 V 如果你把它应用到 V - 它什么都不做。

迅速:

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

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

Superview这是一个视图,如果将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];
    }
}

这个答案(以及同一问题的其他一些答案)可以帮助您理清思路。

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

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

让我们觉得我的mainViewCGRect(0.0, 0.0, 250, 250.0)我想在如添加一个按钮大小的宽度和高度50 subView我的mainView (至的左上角),它的边框看起来像CGRect(-50, -50, 50, 50)mainView在这种情况下我们无法与此按钮交互,这是如何解决此问题的示例

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

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard !clipsToBounds && !isHidden && alpha > 0 else { return self.superview }
    for member in subviews.reversed() {
        let subPoint = member.convert(point, from: self)
        guard let result = member.hitTest(subPoint, with: event) else { continue }
        return result
    }
    return self.bounds.contains(point) ? self : nil
}

我已经在我自己的应用程序中测试并使用了这段代码,它运行良好。

jnic 回答第一部分的 Swift 5 版本:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let radius: CGFloat = 100

    let frame = CGRect(
        x: -radius,
        y: -radius,
        width: frame.width + radius * 2,
        height: frame.height + radius * 2
    )

    if (frame.contains(point)) {
        return self
    }
    
    return nil
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM