【问题标题】:Receiving touch events outside bounds接收超出范围的触摸事件
【发布时间】:2016-10-16 21:45:09
【问题描述】:

之前也有类似的问题,我参考过Capturing touches on a subview outside the frame of its superview using hitTest:withEvent:Delivering touch events to a view outside the bounds of its parent view。但他们似乎没有回答我的特殊问题。

我有一个具有以下结构的自定义控件:

+-------------------------------+
|           UIButton            |
+-------------------------------+
|                          |
|                          |
|        UITableView       |
|                          |
|                          |
+------------------------- +

自定义控件覆盖UIButton 子类并将UITableView 添加为其子视图。这个想法是让整个控件像下拉菜单一样。当按下UIButton 时,UITableView 将下拉并启用选择。

如果控件是最上面的UIView 的直接子控件,则这一切都适用于UIButton 中覆盖的UIButton 中的hitTest,如上面Apple 的问答链接中所述。但是,如果控件位于另一个视图层次结构中,则UITableView 不会接收到触摸事件。

以下是使用的hitTest代码:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
    // Convert the point to the target view's coordinate system.
    // The target view isn't necessarily the immediate subview
    let pointForTargetView = self.spinnerTableView.convertPoint(point, fromView:self) 

    if (CGRectContainsPoint(self.spinnerTableView.bounds, pointForTargetView)) {
        // The target view may have its view hierarchy,
        // so call its hitTest method to return the right hit-test view
        return self.spinnerTableView.hitTest(pointForTargetView, withEvent:event);
    }
    return super.hitTest(point, withEvent: event)
}

编辑:

很抱歉,由于需要立即关注其他一些任务,因此无法查看答案并检查它们是否解决了问题。我会检查它们并接受一个有帮助的。感谢您的耐心等待。

【问题讨论】:

    标签: ios swift uikit


    【解决方案1】:

    如你所说:

    但是,如果控件位于另一个视图层次结构中,则 UITableView 没有接收到触摸事件。

    即使你让它工作了,如果你在另一个 UIView 下查看了这个控件怎么办?

    这样我们就有了为视图层次结构转换点的负担,我的建议是,正如您在this control 中看到的那样,它将表格视图添加到控件本身所在的同一层次结构中。 (例如,它将表格视图添加到添加此控件的控制器上)

    链接的一部分:

    -(void)textFieldDidBeginEditing:(UITextField *)textField
    {
        [self setupSuggestionList];
        [suggestionListView setHidden:NO];
    
        // Add list to the super view.
        if(self.dataSourceDelegate && [self.dataSourceDelegate isKindOfClass:UIViewController.class])
        {
            [((UIViewController *)self.dataSourceDelegate).view addSubview:suggestionListView];
        }
    
        // Setup list as per the given direction
        [self adjustListFrameForDirection:dropDownDirection];
    }
    

    希望有帮助!

    【讨论】:

    • 有道理。不过我没试过。
    【解决方案2】:

    我认为你应该重写 pointInside 实现:

    class Control: UIButton {
    
        var dropdown: Bool = false
    
        override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
            if dropdown {
                return CGRectContainsPoint(CGRect(x: 0, y:0, width: self.bounds.width, self.bounds.height + spinnerTableView.frame.height), point)
            }
            return super.pointInside(point, withEvent: event)
        }
    }
    

    这样,superview调用控件的hittest,当触摸超出控件边界时,它不会返回nil。

    但是还有一个问题,如果控件在另一个视图层次结构中,UITableView可能没有接收到触摸事件。

    我觉得这个问题是正常的,你无法决定其他视图的pointInside是否受控制,如果控件superviewhitTest返回nil,控件hitTest方法将不会被调用。 除非你重写了所有查看pointInside控制的方法,但这是不现实的。

    【讨论】:

      【解决方案3】:

      我在想UIResponder,比如:

      override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
              // Convert the point to the target view's coordinate system.
              // The target view isn't necessarily the immediate subview
      
              var responder: UIResponder = self
              while responder.nextResponder() != nil {
                  responder = responder.nextResponder()!
                  if responder is UITableView {
                      // Got UITableView
                      break;
                  }
              }
              let pointForTargetView = responder.convertPoint(point, fromView:self)
              if (CGRectContainsPoint(responder.bounds, pointForTargetView)) {
                  // The target view may have its view hierarchy,
                  // so call its hitTest method to return the right hit-test view
                  return self.responder.hitTest(pointForTargetView, withEvent:event);
              }
              return super.hitTest(point, withEvent: event)
      }
      

      如果此方法不起作用,请尝试使用superview convertPoint

      当您确定 self.spinnerTableView 不是 nil 并且它是您的桌子时,请执行以下操作:

      let pointForTargetView = self.spinnerTableView.superView.convertPoint(point, fromView:self) 
      

      【讨论】:

      • 不,之前实现的问题是该点未正确转换为UITableView 坐标系。因此,即使在上述情况下,CGRectContainsPoint 也无法识别位于UITableView 范围内的点。
      • 谢谢,但我需要一个通用的方法来做到这一点。与控件所在的层级数无关。
      • 我不明白您是否已经解决了您的问题,或者只需要高级语法代码即可。正如您现在有评论一样,似乎问题已解决并且您知道该怎么做,但是您想要一种方法来涵盖所有超级视图的可能性……您能更好地解释您的评论吗?谢谢(对不起,我不是英语,所以也许任何事情都可以逃脱…… )
      • 对不起,含糊不清的评论。更新的答案没有解决问题。据我了解,您建议从超级视图的坐标系转换点。如果控件更深一层,这可能起作用。我需要一个可行的解决方案并且它应该适用于所有情况。我将尝试发布一个示例项目,以便您在发布答案之前尝试您的建议。
      【解决方案4】:

      您需要继承 UIView,并将其用作 UIViewController 的根视图。

      class HitTestBottomView: UIView {
      
        override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
      
          if  let hitTestView  =    self.getHitTestButtonFrom(self){
              let pointForHitTestView = hitTestView.convertPoint(point, fromView: self)
              let pointForHitTestTableView = hitTestView.spinnerTableView.convertPoint(point, fromView: self)
      
              if CGRectContainsPoint(hitTestView.bounds, pointForHitTestView) ||  CGRectContainsPoint(hitTestView.spinnerTableView.bounds, pointForHitTestTableView){
                  return hitTestView.hitTest(pointForHitTestView, withEvent: event)
              }
          }
      
          return super.hitTest(point, withEvent: event)
        }
      
        func getHitTestButtonFrom(view:UIView) ->HitTestButton?{
          if let testView = view as? HitTestButton {
              return testView
          }
      
          for  subView  in view.subviews {
              if let testView = self.getHitTestButtonFrom(subView) {
                  return testView
              }
          }
          return nil
        }
      }
      

      【讨论】:

      • 这个想法是将控件用作任何 UIView 层次结构中的可重用组件。因此,将其用作根视图不是首选解决方案。
      【解决方案5】:

      你必须执行

      func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool
      

      为您的UIButton 并检查UITableView 边界。因为hitTest 首先检查此点击点是否在您的视图范围内,然后为您的视图调用hitTest。所以你必须实现pointInside 并为你的界限返回true。

      advanced scroll views and touch handling techniques wwdc的WWDC

      【讨论】:

      • 这是我做的第一件事。但正如所解释的,问题在于将点转换为表格视图的坐标系。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-27
      • 1970-01-01
      相关资源
      最近更新 更多