From 688bb177777ab0f26696fde7633f928e14f6f348 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 14 Mar 2016 04:07:33 -0700 Subject: [PATCH] Fixed sticky header touch handling bug Summary:In order to ensure that the docked sticky header in a ListView receives touches correctly, RCTScrollView has a custom hitTest implementation that checks the sticky headers for touches prior to checking any other views. There was a bug in this implementation that meant that sticky views would get touch priority even if the touch was outside the bounds of the scrollView. This meant that sticky headers that scrolled off the top of the list would intercept touches intended for views placed above the scrollView. This diff fixes that bug by checking that the touch is inside the scrollview before checking for sticky header touches. I've also limited the custom hit test logic to just the currently docked header, as the other sticky header views do not require special treatment. Reviewed By: javache Differential Revision: D3041236 fb-gh-sync-id: a2a3474dda03d5b51688bd575195a67956184bbe shipit-source-id: a2a3474dda03d5b51688bd575195a67956184bbe --- React/Views/RCTScrollView.m | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index c38e6ed4ca8dd8..773d9bcdaeb34f 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -151,6 +151,9 @@ @interface RCTCustomScrollView : UIScrollView @implementation RCTCustomScrollView +{ + __weak UIView *_dockedHeaderView; +} - (instancetype)initWithFrame:(CGRect)frame { @@ -335,6 +338,7 @@ - (void)dockClosestSectionHeader } currentHeader.transform = CGAffineTransformMakeTranslation(0, yOffset); currentHeader.layer.zPosition = ZINDEX_STICKY_HEADER; + _dockedHeaderView = currentHeader; if (previousHeader) { // The previous header sits right above the currentHeader's initial position @@ -349,22 +353,14 @@ - (void)dockClosestSectionHeader - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { - __block UIView *hitView; - - NSArray *subviews = [self contentView].reactSubviews; - NSUInteger subviewCount = subviews.count; - [_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:^(NSUInteger idx, BOOL *stop) { - if (idx >= subviewCount) { - *stop = YES; - return; + if (_dockedHeaderView && [self pointInside:point withEvent:event]) { + CGPoint convertedPoint = [_dockedHeaderView convertPoint:point fromView:self]; + UIView *hitView = [_dockedHeaderView hitTest:convertedPoint withEvent:event]; + if (hitView) { + return hitView; } - UIView *stickyHeader = subviews[idx]; - CGPoint convertedPoint = [stickyHeader convertPoint:point fromView:self]; - hitView = [stickyHeader hitTest:convertedPoint withEvent:event]; - *stop = (hitView != nil); - }]; - - return hitView ?: [super hitTest:point withEvent:event]; + } + return [super hitTest:point withEvent:event]; } - (void)setRefreshControl:(RCTRefreshControl *)refreshControl @@ -897,7 +893,7 @@ - (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart _onRefreshStart = [onRefreshStart copy]; if (!_scrollView.refreshControl) { - RCTRefreshControl *refreshControl = [[RCTRefreshControl alloc] init]; + RCTRefreshControl *refreshControl = [RCTRefreshControl new]; [refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged]; _scrollView.refreshControl = refreshControl; }