Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIView. Why Does A Subviews Outside its Parent's Extent Not Receive Touches?

I have a simple - trivial - UIView parent/child hierarchy. One parent (UIView). One child (UIButton). The parents bounds are smaller then it's child's bounds so that a portion of the child extends beyond the bounding box of its parent.

Here's the problem: Those portions of the child outside the bbox of the parent do not receive touches. Only tapping within the bbox of the parent allows the child button to receive touches.

Can someone please suggest a fix/workaround?

UPDATE

For those following this question, here is the solution I implemented as a result of @Bastians most excellent answer:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {

    BOOL isInside = [super pointInside:point withEvent:event];

    // identify the button view subclass
    UIButton *b = (UIButton *)[self viewWithTag:3232];
    CGPoint inButtonSpace = [self convertPoint:point toView:b];

    BOOL isInsideButton = [b pointInside:inButtonSpace withEvent:nil];

    if (isInsideButton) {

        return isInsideButton;

    } // if (YES == isInsideButton)

    return isInside;        
}
like image 374
dugla Avatar asked Apr 30 '11 20:04

dugla


4 Answers

The problem is the responder chain. When you touch the display it will go down from the parents to the childen.

So .. when you touch the screen the parent will see that the touch is outside of it's own bounds and so the children will not even asked.

The function that does that is the hitTest. If you have your own UIView class you can overwrite it and return the button by yourself.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
like image 184
Bastian Avatar answered Nov 11 '22 13:11

Bastian


Per Apple’s own documentation, the simplest and most reliable way I have found to do this is to override hitTest:withEvent: in the superclass of your clipped view to look like the following:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    // Convert the point to the target view's coordinate system.
    // The target view isn't necessarily the immediate subview
    CGPoint pointForTargetView = [self.targetView convertPoint:point fromView:self];

    if (CGRectContainsPoint(self.targetView.bounds, pointForTargetView)) {

        // The target view may have its view hierarchy,
        // so call its hitTest method to return the right hit-test view
        return [self.targetView hitTest:pointForTargetView withEvent:event];
    }

    return [super hitTest:point withEvent:event];
}
like image 24
Tim Arnold Avatar answered Nov 11 '22 11:11

Tim Arnold


Precondition:

You have a UIButton(named as button1) inside a UIView(named as container), and button1 is partially outside the container's bounds.

Problem:

the part outside the container of button1 will not response click.

Solution:

subclass your container from UIView:

class Container: UIView {
    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {

        let closeButton = viewWithTag(10086) as! UIButton  //<-- note the tag value
        if closeButton.pointInside(convertPoint(point, toView: closeButton), withEvent: event) {
            return true
        }
        return super.pointInside(point, withEvent: event)
    }
}

Don't forget to give your button1 a tag of 10086

like image 2
laoyur Avatar answered Nov 11 '22 12:11

laoyur


  • @dugla, thank you for the question!
  • @Bastian and @laoyur, thanks to you for answers!

Swift 4

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

    if yourChildView.point(inside: convert(point, to: yourChildView), with: event) {
        return true
    }

    return super.point(inside: point, with: event)
}
like image 2
Roman Romanenko Avatar answered Nov 11 '22 12:11

Roman Romanenko