接收界外的触摸事件

时间:2016-06-15 16:40:30

标签: ios swift uikit

之前有过类似的问题,我提到了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将下拉并启用选择。

如果控件是最顶层的hitTest的直接子项,如上面的Apple的Q& A链接中所述UIButton覆盖UIView这一切都正常。但是,如果控件位于另一个视图层次结构中,则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)
}

修改

我很抱歉没有能够查看答案并检查他们是否解决了问题,因为其他一些任务需要立即引起注意。我会检查它们并接受一个有帮助的。谢谢你的耐心等待。

5 个答案:

答案 0 :(得分:5)

正如你所说:

  

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

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

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

链接中的一部分:

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

希望有所帮助!

答案 1 :(得分:4)

我认为你应该覆盖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可能无法接收触摸事件。

我认为这个问题是正常的,如果控件超级视图hitTest返回nil,则控件hitTest方法无法控制其他视图中的pointInside与否不会被叫。 除非你覆盖了所有受控制的pointInside方法,但这是不现实的。

答案 2 :(得分: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)
}

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

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

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

答案 3 :(得分:1)

你需要subClass 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
  }
}

答案 4 :(得分:1)

你必须实施

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

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

WWDC for advanced scroll views and touch handling techniques wwdc