Interaction beyond bounds of UIView

2019-01-04 17:35发布

Is it possible for a UIButton (or any other control for that matter) to receive touch events when the UIButton's frame lies outside of it's parent's frame? Cause when I try this, my UIButton doesn't seem to be able to receive any events. How do I work around this?

8条回答
时光不老,我们不散
2楼-- · 2019-01-04 18:13

Why this is happening?

This is because when your subview lies outside of your superview's bounds, touch events that actually happens on that subview will not be delivered to that subview. However, it WILL be delivered to its superview.

Regardless of whether or not subviews are clipped visually, touch events always respect the bounds rectangle of the target view’s superview. In other words, touch events occurring in a part of a view that lies outside of its superview’s bounds rectangle are not delivered to that view. Link

What you need to do?

When your superview receives the touch event mentioned above, you'll need to tell UIKit explicitly that my subview should be the one to receive this touch event.

What about the code?

In your superview, implement func hitTest(_ point: CGPoint, with event: UIEvent?)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isHidden || alpha == 0 || clipsToBounds { return nil }
        // convert the point into subview's coordinate system
        let subviewPoint = self.convert(point, to: subview)
        // if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event
        if ! subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
        return nil
    }
查看更多
Luminary・发光体
3楼-- · 2019-01-04 18:16

Yes. You can override the hitTest:withEvent: method to return a view for a larger set of points than that view contains. See the UIView Class Reference.

Edit: Example:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(-radius, -radius,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}

Edit 2: (After clarification:) In order to ensure that the button is treated as being within the parent's bounds, you need to override pointInside:withEvent: in the parent to include the button's frame.

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if (CGRectContainsPoint(self.view.bounds, point) ||
        CGRectContainsPoint(button.view.frame, point))
    {
        return YES;
    }
    return NO;
}

Note the code just there for overriding pointInside is not quite correct. As Summon explains below, do this:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
    if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
        return YES;

    return [super pointInside:point withEvent:event];
    }

Note that you'd very likely do it with self.oversizeButton as an IBOutlet in this UIView subclass; then you can just drag the "oversize button" in question, to, the special view in question. (Or, if for some reason you were doing this a lot in a project, you'd have a special UIButton subclass, and you could look through your subview list for those classes.) Hope it helps.

查看更多
登录 后发表回答