Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fire UITapGestureRecognizer on a child view that is partially clipped by parent view?

Setup: I have parent view A. It has a child view B. B is partially inside the bounds of A, but partially outside (A has clipsToBounds=false). I have attached UITapGestureRecognizer (UITGR) to B.

Observed: UITGR fires OK when I tap on the portion of B that is within the bounds of A. UITGR does NOT fire when I tap on the portion of B that is outside the bounds of A.

Expected/question: How do I make UITGR fire when I tap on the portion of B that is outside the bounds of A?

like image 959
Jaanus Avatar asked May 07 '12 04:05

Jaanus


2 Answers

This quote will answer your question as to why it behaves like that:

Touch events. The window object uses hit-testing and the responder chain to find the view to receive the touch event. In hit-testing, a window calls hitTest:withEvent: on the top-most view of the view hierarchy; this method proceeds by recursively calling pointInside:withEvent: on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view. (source)

One workaround is to create your own UITapInSubviewsView with the following definition for hitTest:

(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSEnumerator *reverseE = [self.subviews reverseObjectEnumerator];
    UIView *iSubView;
    while ((iSubView = [reverseE nextObject])) {

        UIView *viewWasHit = [iSubView hitTest:[self convertPoint:point toView:iSubView] withEvent:event];
        if(viewWasHit)
           return viewWasHit;
     }
     return [super hitTest:point withEvent:event];
}

Then you use this class for your parent view.

(I found this code in a post from S.O. a few weeks ago, but I cannot seem to find it anymore; so just copied it from my project).

like image 193
sergio Avatar answered Oct 30 '22 22:10

sergio


Update of @sergio's answer for Swift 4.2:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let reversedSubviews = subviews.reversed()
    let hitSubview = reversedSubviews
        .first(where: { $0.hitTest(convert(point, to: $0), with: event) != nil })
    if hitSubview != nil {
        return hitSubview
    }

    return super.hitTest(point, with: event)
}
like image 37
damjandd Avatar answered Oct 30 '22 22:10

damjandd