NePheus NePheus -4 years ago 67
iOS Question

Capturing touches on a subview outside the frame of its superview (over multiple layers)

I have a similar problem described in this thread: Capturing touches on a subview outside the frame of its superview using hitTest:withEvent:

Situation

We are developing an app in Xamarin iOS. We have a custom view to create an own autoComplete input field, containing an UiTextField and an UiTableView beneath to show the results.

To achieve a better modularity we have another custom view called LabeledContainer. Inside it, I can set a label and add content to the bottom view, which is the autoComplete in this case.

AutoComplete LabeledContainer
+---------------+ +---------------+
| UiView | | UiView |
|+-------------+| |+-------------+|
|| UiTextField || || UiLabel ||
|+-------------+| |+-------------+|
|+-------------+| |+-------------+|
|| UiTableView || || UiView ||
|+-------------+| |+-------------+|
+---------------+ +---------------+

The following gets rendered:
MAINVIEW
+------------------------------+
| |
| |
| |
| |
|+----------------------------+|
|| LabeledContainer ||
||+--------------------------+||
||| Label |||
||+--------------------------+||
||+--------------------------+||
||| Content |||
|||+------------------------+|||
|||| AutoComplete ||||
|||+------------------------+|||
||+--------------------------+||
|+----------------------------+|
| |
| |
| |
| |
+------------------------------+


Problem

I have problems with the "hitarea" because the autoComplete dropdown (tableView) is outside of the custom views frame. The frame just takes the height of the input field and the dropdown overlaps it at the bottom. So I added the following method to add the dropdown to the hitArea.

public override bool PointInside(CGPoint point, UIEvent uievent)
{
var bounds = Bounds;
bounds.Height += DROPDOWN.Bounds.Height;
return bounds.Contains(point);
}


But this only works if I add my autoComplete as subChild of the mainView, because PointInside gets triggered there. If I add the autoComplete to the labeledContainer, which has afterwards a frame height of label + inputfield of the autocomplete, the method PointsInside gets never triggered. This is the case because the labeledContainer isn't high enough, right? So PointsInside of a view just gets triggered when the superView gets touched? But I can't make the labeledContainer or autoComplete higher to prevent pushing other views.

I tried to add the PointsInside method also to the labeledContainer, but it didn't solve my problem, because its not touching the labeledContainer frame and the autoComplete PointsInside is never called.

Answer Source

By default, UIViews do not receive touch events outside of their superviews. However, you can subclass your container view to detect touches outside of its frame. Container view should forward unhandled touch events to its subviews.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint pointForTargetView = [self.autoCompleteView convertPoint:point fromView:self];

    if (CGRectContainsPoint(self.autoCompleteView.bounds, pointForTargetView)) {
        return [self.autoCompleteView hitTest:pointForTargetView withEvent:event];
    }
    return [super hitTest:point withEvent:event];
}

From Apple's documentation:

Points that lie outside the receiver’s bounds are never reported as hits, even if they actually lie within one of the receiver’s subviews. This can occur if the current view’s clips​To​Bounds property is set to NO and the affected subview extends beyond the view’s bounds.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download