-->

UIView. Why Does A Subviews Outside its Parent'

2019-01-13 07:32发布

问题:

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

回答1:

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


回答2:

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


回答3:

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



回答4:

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


回答5:

I had the same exact problem at hand. You only need to override:

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

Here is working code for a custom UIView subclass created solely to be tappable for its out-of-bounds children.

@implementation ARQViewToRightmost

// We should not need to override this one !!!
/*-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if ([self isInside:point])
        return self;
    return nil;
}*/

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if ([self isInside:point])
        return YES; // That answer will make hitTest continue recursively probing the view's subviews for hit
    return NO;
}

// Check to see if the point is within the bounds of the view. We've extended the bounds to the max right
// Make sure you do not have any other sibling UIViews to the right of this one as they will never receive events
- (BOOL) isInside:(CGPoint)point
{
    CGFloat maxWidth = CGFLOAT_MAX;
    CGRect rect = CGRectMake(0, 0, maxWidth, self.frame.size.height);
    if (CGRectContainsPoint(rect, point))
        return YES;
    return NO;
}

@end