【问题标题】:Interaction beyond bounds of UIView超越 UIView 的交互
【发布时间】:2011-07-22 22:11:22
【问题描述】:

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

【问题讨论】:

标签: iphone objective-c cocoa uiview uibutton


【解决方案1】:

是的。您可以覆盖 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];
    }

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

【讨论】:

  • 你能帮助我进一步用一些示例代码正确地实现它吗?我还阅读了参考资料,由于以下行,我很困惑:“位于接收器边界之外的点永远不会被报告为命中,即使它们实际上位于接收器的子视图之一内。子视图可能在视觉上超出边界如果父视图的 clipsToBounds 属性设置为 NO,则为它们的父视图。但是,命中测试总是会忽略父视图边界之外的点。"
  • 尤其是这部分让我觉得这不是我需要的解决方案:“但是,命中测试总是忽略父视图边界之外的点”.. 但我当然可能是错的,我发现苹果引用有时真的很混乱..
  • 啊,我明白了。您需要覆盖父级的 pointInside:withEvent: 以包含按钮的框架。我将在上面的答案中添加一些示例代码。
  • 有没有办法在不覆盖 UIView 的方法的情况下做到这一点?例如,如果您的视图对 UIKit 完全通用并通过 Nib 进行初始化,您能否在 UIViewController 中覆盖这些方法?
  • hitTest:withEvent:pointInside:withEvent: 当我按下位于父母范围之外的按钮时,我没有调用该区域。有什么问题?
【解决方案2】:

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

最好的

【讨论】:

    【解决方案3】:

    斯威夫特:

    其中 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】:

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

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

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

      【讨论】:

        【解决方案5】:

        就我而言,我有一个包含UIButtonUICollectionViewCell 子类。我在单元格上禁用了clipsToBounds,并且按钮在单元格边界之外可见。但是,按钮没有接收到触摸事件。通过使用 Jack 的回答,我能够检测到按钮上的触摸事件: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 类中。
        • 但是你为什么要把按钮注入一个被覆盖的方法???
        【解决方案6】:

        对于我(对于其他人)来说,答案是不是覆盖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];
            }
        }
        

        This answer(以及其他几个就同一问题)可以帮助您理清思路。

        【讨论】:

          【解决方案7】:

          为什么会这样?

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

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

          您需要做什么?

          当你的 superview 接收到上面提到的触摸事件时,你需要明确告诉 UIKit 我的子视图应该是接收这个触摸事件的那个。

          代码呢?

          在你的superview中,实现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 - 它什么也不做。

          【讨论】:

          • 谢谢你!我已经花费了数小时和数小时来解决由此产生的问题。我真的很感谢你花时间发布这个。 :)
          • @ScottZhu :我确实在您的回答中添加了一个重要的问题。当然,您应该随时删除或更改我的添加!再次感谢!
          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

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

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

            在您的 MainView(按钮容器视图) 覆盖 hitTestfunction,如其他答案中所述,但此处反转子视图顺序并将命中点转换为该子视图点,然后仅使用 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
            }
            

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

            【讨论】:

              【解决方案10】:

              Swift 5 版本第一部分jnic's answer

              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
              }
              

              【讨论】:

                猜你喜欢
                • 2019-03-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-12-14
                • 2011-06-01
                • 2011-03-28
                相关资源
                最近更新 更多