Skip to content

Commit 850760a

Browse files
j-piaseckifacebook-github-bot
authored andcommitted
Overwrite betterHitTest in RCTScrollViewComponentView instead of changing layout metrics of the container view (facebook#50027)
Summary: Pull Request resolved: facebook#50027 Changelog: [IOS][CHANGED] - Overwrite betterHitTest in RCTScrollViewComponentView instead of changing layout metrics of the container view ## Summary: In facebook#49855 I changed the container view of `RCTScrollViewComponentView` to be `RCTViewComponentView` instead of `UIView` so the touches would bass through its `betterHitTest` implementation (along with udating its layout metrics so the right path in the function is chosen). This resulted in some issues and the alternative approach of customizing the hit testing of the ScrollView itself might be a better approach. This PR changes overwrites the `betterHitTest` method in a way that the `containerView` is entirely skipped during hit testing, instead forwarding the call to its children. This way, it won't prevent touches outside its frame from being delivered to children that extend out of the frame. Reviewed By: cipolleschi Differential Revision: D71187882 fbshipit-source-id: 9d0c79048f389b9bee37dea1e59226b54ddbe6f2
1 parent d350b69 commit 850760a

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ @interface RCTScrollViewComponentView () <
8989

9090
@implementation RCTScrollViewComponentView {
9191
ScrollViewShadowNode::ConcreteState::Shared _state;
92-
LayoutMetrics _lastContentContainerLayoutMetrics;
9392
CGSize _contentSize;
9493
NSTimeInterval _lastScrollEventDispatchTime;
9594
NSTimeInterval _scrollEventThrottle;
@@ -133,7 +132,7 @@ - (instancetype)initWithFrame:(CGRect)frame
133132
_automaticallyAdjustKeyboardInsets = NO;
134133
[self addSubview:_scrollView];
135134

136-
_containerView = [[RCTViewComponentView alloc] initWithFrame:CGRectZero];
135+
_containerView = [[UIView alloc] initWithFrame:CGRectZero];
137136
[_scrollView addSubview:_containerView];
138137

139138
[self.scrollViewDelegateSplitter addDelegate:self];
@@ -469,17 +468,44 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &
469468
}
470469

471470
_contentSize = contentSize;
472-
LayoutMetrics newContentContainerLayoutMetrics = LayoutMetrics{
473-
.frame = {.origin = data.contentBoundingRect.origin, .size = data.getContentSize()}, .overflowInset = {.top = 1}};
474-
[_containerView updateLayoutMetrics:newContentContainerLayoutMetrics
475-
oldLayoutMetrics:_lastContentContainerLayoutMetrics];
476-
_lastContentContainerLayoutMetrics = newContentContainerLayoutMetrics;
471+
_containerView.frame = CGRect{RCTCGPointFromPoint(data.contentBoundingRect.origin), contentSize};
477472

478473
[self _preserveContentOffsetIfNeededWithBlock:^{
479474
self->_scrollView.contentSize = contentSize;
480475
}];
481476
}
482477

478+
- (UIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event
479+
{
480+
// This is the same algorithm as in the RCTViewComponentView with the exception of
481+
// skipping the immediate child (_containerView) and checking grandchildren instead.
482+
// This prevents issues with touches outside of _containerView being ignored even
483+
// if they are within the bounds of the _containerView's children.
484+
485+
if (!self.userInteractionEnabled || self.hidden || self.alpha < 0.01) {
486+
return nil;
487+
}
488+
489+
BOOL isPointInside = [self pointInside:point withEvent:event];
490+
491+
BOOL clipsToBounds = _containerView.clipsToBounds;
492+
493+
clipsToBounds = clipsToBounds || _layoutMetrics.overflowInset == EdgeInsets{};
494+
495+
if (clipsToBounds && !isPointInside) {
496+
return nil;
497+
}
498+
499+
for (UIView *subview in [_containerView.subviews reverseObjectEnumerator]) {
500+
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
501+
if (hitView) {
502+
return hitView;
503+
}
504+
}
505+
506+
return isPointInside ? self : nil;
507+
}
508+
483509
/*
484510
* Disables programmatical changing of ScrollView's `contentOffset` if a touch gesture is in progress.
485511
*/

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ NS_ASSUME_NONNULL_BEGIN
7474
oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics NS_REQUIRES_SUPER;
7575
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask NS_REQUIRES_SUPER;
7676
- (void)prepareForRecycle NS_REQUIRES_SUPER;
77+
- (UIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event;
7778

7879
/*
7980
* This is a fragment of temporary workaround that we need only temporary and will get rid of soon.

0 commit comments

Comments
 (0)