From fab5fffbb3eb8668c9202dec5e770330d49880b0 Mon Sep 17 00:00:00 2001 From: Peter Argany Date: Mon, 23 Jul 2018 13:17:33 -0700 Subject: [PATCH] Fixed OSS scroll view bug caused by FBPullToRefresh Summary: When I bridged FBPullToRefresh to RN, the integration with ScrollView caused a bug on OSS TLDR; assuming that a scrollview subview that implemented UIScrollViewDelegate protocol was a custom PTR was a bad idea. This caused some scrollviews to break in OSS. The solution is to define a more explicit protocol. Further details here: https://github.com/facebook/react-native/issues/20324 Reviewed By: mmmulani Differential Revision: D8953893 fbshipit-source-id: 98cdc7fcced41d9e98e77293a03934f10c798665 --- React/Views/RCTRefreshControl.h | 3 +- React/Views/ScrollView/RCTScrollView.m | 40 +++++++++---------- .../Views/ScrollView/RCTScrollableProtocol.h | 11 +++++ 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/React/Views/RCTRefreshControl.h b/React/Views/RCTRefreshControl.h index fa0a1b494..0218d13d2 100644 --- a/React/Views/RCTRefreshControl.h +++ b/React/Views/RCTRefreshControl.h @@ -8,8 +8,9 @@ #import #import +#import -@interface RCTRefreshControl : UIRefreshControl +@interface RCTRefreshControl : UIRefreshControl @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) RCTDirectEventBlock onRefresh; diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index 58661fa78..4afdb379d 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -155,7 +155,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) @property (nonatomic, assign) BOOL centerContent; #if !TARGET_OS_TV -@property (nonatomic, strong) RCTRefreshControl *rctRefreshControl; +@property (nonatomic, strong) UIView *customRefreshControl; @property (nonatomic, assign) BOOL pinchGestureEnabled; #endif @@ -329,13 +329,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) } #if !TARGET_OS_TV -- (void)setRctRefreshControl:(RCTRefreshControl *)refreshControl +- (void)setCustomRefreshControl:(UIView *)refreshControl { - if (_rctRefreshControl) { - [_rctRefreshControl removeFromSuperview]; + if (_customRefreshControl) { + [_customRefreshControl removeFromSuperview]; } - _rctRefreshControl = refreshControl; - [self addSubview:_rctRefreshControl]; + _customRefreshControl = refreshControl; + [self addSubview:_customRefreshControl]; } - (void)setPinchGestureEnabled:(BOOL)pinchGestureEnabled @@ -443,13 +443,12 @@ static inline void RCTApplyTransformationAccordingLayoutDirection(UIView *view, { [super insertReactSubview:view atIndex:atIndex]; #if !TARGET_OS_TV - if ([view isKindOfClass:[RCTRefreshControl class]]) { - [_scrollView setRctRefreshControl:(RCTRefreshControl *)view]; - } - else if ([view conformsToProtocol:@protocol(UIScrollViewDelegate)]) { - [self addScrollListener:(UIView *)view]; - [_scrollView addSubview:view]; - [_scrollView sendSubviewToBack:view]; + if ([view conformsToProtocol:@protocol(RCTCustomRefreshContolProtocol)]) { + [_scrollView setCustomRefreshControl:(UIView *)view]; + if (![view isKindOfClass:[UIRefreshControl class]] + && [view conformsToProtocol:@protocol(UIScrollViewDelegate)]) { + [self addScrollListener:(UIView *)view]; + } } else #endif { @@ -464,11 +463,12 @@ static inline void RCTApplyTransformationAccordingLayoutDirection(UIView *view, { [super removeReactSubview:subview]; #if !TARGET_OS_TV - if ([subview isKindOfClass:[RCTRefreshControl class]]) { - [_scrollView setRctRefreshControl:nil]; - } else if ([subview conformsToProtocol:@protocol(UIScrollViewDelegate)]) { - [self removeScrollListener:(UIView *)subview]; - [subview removeFromSuperview]; + if ([subview conformsToProtocol:@protocol(RCTCustomRefreshContolProtocol)]) { + [_scrollView setCustomRefreshControl:nil]; + if (![subview isKindOfClass:[UIRefreshControl class]] + && [subview conformsToProtocol:@protocol(UIScrollViewDelegate)]) { + [self removeScrollListener:(UIView *)subview]; + } } else #endif { @@ -519,8 +519,8 @@ static inline void RCTApplyTransformationAccordingLayoutDirection(UIView *view, #if !TARGET_OS_TV // Adjust the refresh control frame if the scrollview layout changes. - RCTRefreshControl *refreshControl = _scrollView.rctRefreshControl; - if (refreshControl && refreshControl.refreshing) { + UIView *refreshControl = _scrollView.customRefreshControl; + if (refreshControl && refreshControl.isRefreshing) { refreshControl.frame = (CGRect){_scrollView.contentOffset, {_scrollView.frame.size.width, refreshControl.frame.size.height}}; } #endif diff --git a/React/Views/ScrollView/RCTScrollableProtocol.h b/React/Views/ScrollView/RCTScrollableProtocol.h index 33ed067ec..f0f512855 100644 --- a/React/Views/ScrollView/RCTScrollableProtocol.h +++ b/React/Views/ScrollView/RCTScrollableProtocol.h @@ -6,6 +6,7 @@ */ #import +#import /** * Contains any methods related to scrolling. Any `RCTView` that has scrolling @@ -28,3 +29,13 @@ - (void)removeScrollListener:(NSObject *)scrollListener; @end + +/** + * Denotes a view which implements custom pull to refresh functionality. + */ +@protocol RCTCustomRefreshContolProtocol + +@property (nonatomic, copy) RCTDirectEventBlock onRefresh; +@property (nonatomic, readonly, getter=isRefreshing) BOOL refreshing; + +@end