Skip to content

Commit

Permalink
Fabric: [RCTViewComponentView betterHitTest:] proper support for `c…
Browse files Browse the repository at this point in the history
…lipToBounds` and `zIndex`

Summary:
@public
Besides `pointerEvents` there are other two props that affect hit-testing mechanism: `zIndex` and `clipToBounds`.
The default UIKit implementation does not take this into an account (it always assume that `clipToBounds` is true and `zIndex` is same). `betterHitTest` does it right.

Reviewed By: sahrens

Differential Revision: D9688876

fbshipit-source-id: dadfd5e5541ddd1a744fbd8c6b10949c0e266069
  • Loading branch information
shergin authored and gengjiawen committed Sep 14, 2018
1 parent 7f6f138 commit 366b211
Showing 1 changed file with 36 additions and 2 deletions.
38 changes: 36 additions & 2 deletions React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -196,18 +196,52 @@ - (void)updateLayoutMetrics:(LayoutMetrics)layoutMetrics
[self invalidateLayer];
}

- (UIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// This is a classic textbook implementation of `hitTest:` with a couple of improvements:
// * It takes layers' `zIndex` property into an account;
// * It does not stop algorithm if some touch is outside the view
// which does not have `clipToBounds` enabled.

if (!self.userInteractionEnabled || self.hidden || self.alpha < 0.01) {
return nil;
}

BOOL isPointInside = [self pointInside:point withEvent:event];

if (self.clipsToBounds && !isPointInside) {
return nil;
}

NSArray<__kindof UIView *> *sortedSubviews =
[self.subviews sortedArrayUsingComparator:^NSComparisonResult(UIView *a, UIView *b) {
// Ensure sorting is stable by treating equal `zIndex` as ascending so
// that original order is preserved.
return a.layer.zPosition > b.layer.zPosition ? NSOrderedDescending : NSOrderedAscending;
}];

for (UIView *subview in [sortedSubviews reverseObjectEnumerator]) {
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
if (hitView) {
return hitView;
}
}

return isPointInside ? self : nil;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
auto viewProps = *std::static_pointer_cast<const ViewProps>(_props);
switch (viewProps.pointerEvents) {
case PointerEventsMode::Auto:
return [super hitTest:point withEvent:event];
return [self betterHitTest:point withEvent:event];
case PointerEventsMode::None:
return nil;
case PointerEventsMode::BoxOnly:
return [self pointInside:point withEvent:event] ? self : nil;
case PointerEventsMode::BoxNone:
UIView *view = [super hitTest:point withEvent:event];
UIView *view = [self betterHitTest:point withEvent:event];
return view != self ? view : nil;
}
}
Expand Down

0 comments on commit 366b211

Please sign in to comment.