diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 4e92411bba..b4dee4c952 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -563,7 +563,11 @@ var styles = StyleSheet.create({ }); if (Platform.OS === 'android') { - var nativeOnlyProps = { nativeOnly : { 'sendMomentumEvents' : true } }; + var nativeOnlyProps = { + nativeOnly: { + sendMomentumEvents: true, + } + }; var AndroidScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps); var AndroidHorizontalScrollView = requireNativeComponent( 'AndroidHorizontalScrollView', @@ -571,7 +575,15 @@ if (Platform.OS === 'android') { nativeOnlyProps ); } else if (Platform.OS === 'ios') { - var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView); + var nativeOnlyProps = { + nativeOnly: { + onMomentumScrollBegin: true, + onMomentumScrollEnd : true, + onScrollBeginDrag: true, + onScrollEndDrag: true, + } + }; + var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps); } module.exports = ScrollView; diff --git a/React/Base/RCTEventDispatcher.h b/React/Base/RCTEventDispatcher.h index 105bb2def8..11045f667a 100644 --- a/React/Base/RCTEventDispatcher.h +++ b/React/Base/RCTEventDispatcher.h @@ -11,7 +11,8 @@ #import "RCTBridge.h" -typedef NS_ENUM(NSInteger, RCTTextEventType) { +typedef NS_ENUM(NSInteger, RCTTextEventType) +{ RCTTextEventTypeFocus, RCTTextEventTypeBlur, RCTTextEventTypeChange, @@ -20,15 +21,6 @@ typedef NS_ENUM(NSInteger, RCTTextEventType) { RCTTextEventTypeKeyPress }; -typedef NS_ENUM(NSInteger, RCTScrollEventType) { - RCTScrollEventTypeStart, - RCTScrollEventTypeMove, - RCTScrollEventTypeEnd, - RCTScrollEventTypeStartDeceleration, - RCTScrollEventTypeEndDeceleration, - RCTScrollEventTypeEndAnimation, -}; - /** * The threshold at which text inputs will start warning that the JS thread * has fallen behind (resulting in poor input performance, missed keys, etc.) diff --git a/React/Views/RCTScrollView.h b/React/Views/RCTScrollView.h index 056e0040f5..7bc9c617c3 100644 --- a/React/Views/RCTScrollView.h +++ b/React/Views/RCTScrollView.h @@ -47,8 +47,19 @@ @property (nonatomic, assign) int snapToInterval; @property (nonatomic, copy) NSString *snapToAlignment; @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices; -@property (nonatomic, copy) RCTDirectEventBlock onRefreshStart; +// NOTE: currently these event props are only declared so we can export the +// event names to JS - we don't call the blocks directly because scroll events +// need to be coalesced before sending, for performance reasons. +@property (nonatomic, copy) RCTDirectEventBlock onScrollBeginDrag; +@property (nonatomic, copy) RCTDirectEventBlock onScroll; +@property (nonatomic, copy) RCTDirectEventBlock onScrollEndDrag; +@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollBegin; +@property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollEnd; +@property (nonatomic, copy) RCTDirectEventBlock onScrollAnimationEnd; + +// Pull-to-refresh support (deprecated - use RCTPullToRefreshControl instead) +@property (nonatomic, copy) RCTDirectEventBlock onRefreshStart; - (void)endRefreshing; @end diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 703de4db30..452554dd1f 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -25,34 +25,34 @@ CGFloat const ZINDEX_STICKY_HEADER = 50; @interface RCTScrollEvent : NSObject -- (instancetype)initWithType:(RCTScrollEventType)type - reactTag:(NSNumber *)reactTag - scrollView:(UIScrollView *)scrollView - userData:(NSDictionary *)userData - coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithEventName:(NSString *)eventName + reactTag:(NSNumber *)reactTag + scrollView:(UIScrollView *)scrollView + userData:(NSDictionary *)userData + coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER; @end @implementation RCTScrollEvent { - RCTScrollEventType _type; UIScrollView *_scrollView; NSDictionary *_userData; uint16_t _coalescingKey; } @synthesize viewTag = _viewTag; +@synthesize eventName = _eventName; -- (instancetype)initWithType:(RCTScrollEventType)type - reactTag:(NSNumber *)reactTag - scrollView:(UIScrollView *)scrollView - userData:(NSDictionary *)userData - coalescingKey:(uint16_t)coalescingKey +- (instancetype)initWithEventName:(NSString *)eventName + reactTag:(NSNumber *)reactTag + scrollView:(UIScrollView *)scrollView + userData:(NSDictionary *)userData + coalescingKey:(uint16_t)coalescingKey { RCTAssertParam(reactTag); if ((self = [super init])) { - _type = type; + _eventName = [eventName copy]; _viewTag = reactTag; _scrollView = scrollView; _userData = userData; @@ -101,20 +101,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) return body; } -- (NSString *)eventName -{ - static NSString *events[] = { - @"scrollBeginDrag", - @"scroll", - @"scrollEndDrag", - @"momentumScrollBegin", - @"momentumScrollEnd", - @"scrollAnimationEnd", - }; - - return events[_type]; -} - - (BOOL)canCoalesce { return YES; @@ -393,7 +379,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) BOOL _allowNextScrollNoMatterWhat; CGRect _lastClippedToRect; uint16_t _coalescingKey; - RCTScrollEventType _lastEmittedEventType; + NSString *_lastEmittedEventName; } @synthesize nativeScrollDelegate = _nativeScrollDelegate; @@ -576,13 +562,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) #pragma mark - ScrollView delegate -#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \ -- (void)delegateMethod:(UIScrollView *)scrollView \ -{ \ - [self sendScrollEventWithType:eventName reactTag:self.reactTag scrollView:scrollView userData:nil]; \ - if ([_nativeScrollDelegate respondsToSelector:_cmd]) { \ - [_nativeScrollDelegate delegateMethod:scrollView]; \ - } \ +#define RCT_SEND_SCROLL_EVENT(_eventName, _userData) { \ + NSString *eventName = NSStringFromSelector(@selector(_eventName)); \ + [self sendScrollEventWithName:eventName scrollView:_scrollView userData:_userData]; \ } #define RCT_FORWARD_SCROLL_EVENT(call) \ @@ -590,10 +572,17 @@ if ([_nativeScrollDelegate respondsToSelector:_cmd]) { \ [_nativeScrollDelegate call]; \ } -RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndScrollingAnimation, RCTScrollEventTypeEndDeceleration) -RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, RCTScrollEventTypeStartDeceleration) -RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndDecelerating, RCTScrollEventTypeEndDeceleration) -RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) +#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \ +- (void)delegateMethod:(UIScrollView *)scrollView \ +{ \ + RCT_SEND_SCROLL_EVENT(eventName, nil); \ + RCT_FORWARD_SCROLL_EVENT(delegateMethod:scrollView); \ +} + +RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndScrollingAnimation, onMomentumScrollEnd) //TODO: shouldn't this be onScrollAnimationEnd? +RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, onMomentumScrollBegin) +RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndDecelerating, onMomentumScrollEnd) +RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll) - (void)scrollViewDidScroll:(UIScrollView *)scrollView { @@ -615,10 +604,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) NSArray *childFrames = [self calculateChildFramesData]; // Dispatch event - [self sendScrollEventWithType:RCTScrollEventTypeMove - reactTag:self.reactTag - scrollView:scrollView - userData:@{@"updatedChildFrames": childFrames}]; + RCT_SEND_SCROLL_EVENT(onScroll, (@{@"updatedChildFrames": childFrames})); // Update dispatch time _lastScrollDispatchTime = now; @@ -662,14 +648,12 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { _allowNextScrollNoMatterWhat = YES; // Ensure next scroll event is recorded, regardless of throttle - [self sendScrollEventWithType:RCTScrollEventTypeStart reactTag:self.reactTag scrollView:scrollView userData:nil]; + RCT_SEND_SCROLL_EVENT(onScrollBeginDrag, nil); RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginDragging:scrollView); } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { - - // snapToInterval // An alternative to enablePaging which allows setting custom stopping intervals, // smaller than a full page size. Often seen in apps which feature horizonally @@ -720,8 +704,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) @"y": @(targetContentOffset->y) } }; - [self sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:userData]; - + RCT_SEND_SCROLL_EVENT(onScrollEndDrag, userData); RCT_FORWARD_SCROLL_EVENT(scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset); } @@ -732,13 +715,13 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { - [self sendScrollEventWithType:RCTScrollEventTypeStart reactTag:self.reactTag scrollView:scrollView userData:nil]; + RCT_SEND_SCROLL_EVENT(onScrollBeginDrag, nil); RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginZooming:scrollView withView:view); } - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale { - [self sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:nil]; + RCT_SEND_SCROLL_EVENT(onScrollEndDrag, nil); RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndZooming:scrollView withView:view atScale:scale); } @@ -922,20 +905,19 @@ RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, scrollIndicatorInsets, UIE [_scrollView.refreshControl endRefreshing]; } -- (void)sendScrollEventWithType:(RCTScrollEventType)type - reactTag:(NSNumber *)reactTag +- (void)sendScrollEventWithName:(NSString *)eventName scrollView:(UIScrollView *)scrollView userData:(NSDictionary *)userData { - if (_lastEmittedEventType != type) { + if (![_lastEmittedEventName isEqualToString:eventName]) { _coalescingKey++; - _lastEmittedEventType = type; + _lastEmittedEventName = [eventName copy]; } - RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithType:type - reactTag:reactTag - scrollView:scrollView - userData:userData - coalescingKey:_coalescingKey]; + RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithEventName:eventName + reactTag:self.reactTag + scrollView:scrollView + userData:userData + coalescingKey:_coalescingKey]; [_eventDispatcher sendEvent:scrollEvent]; } @@ -945,11 +927,13 @@ RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, scrollIndicatorInsets, UIE - (void)sendFakeScrollEvent:(NSNumber *)reactTag { - RCTScrollEvent *fakeScrollEvent = [[RCTScrollEvent alloc] initWithType:RCTScrollEventTypeMove - reactTag:reactTag - scrollView:nil - userData:nil - coalescingKey:0]; + // Use the selector here in case the onScroll block property is ever renamed + NSString *eventName = NSStringFromSelector(@selector(onScroll)); + RCTScrollEvent *fakeScrollEvent = [[RCTScrollEvent alloc] initWithEventName:eventName + reactTag:reactTag + scrollView:nil + userData:nil + coalescingKey:0]; [self sendEvent:fakeScrollEvent]; } diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index 77f916b4e8..2261c4ba88 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -73,6 +73,12 @@ RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString) RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint) RCT_EXPORT_VIEW_PROPERTY(onRefreshStart, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onScrollBeginDrag, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onScrollEndDrag, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollBegin, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onScrollAnimationEnd, RCTDirectEventBlock) RCT_EXPORT_METHOD(getContentSize:(nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback) @@ -161,16 +167,4 @@ RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag }]; } -- (NSArray *)customDirectEventTypes -{ - return @[ - @"scrollBeginDrag", - @"scroll", - @"scrollEndDrag", - @"scrollAnimationEnd", - @"momentumScrollBegin", - @"momentumScrollEnd", - ]; -} - @end diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h index c570b93ff7..021cc21dd9 100644 --- a/React/Views/RCTViewManager.h +++ b/React/Views/RCTViewManager.h @@ -61,7 +61,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary *)customBubblingEventTypes; +- (NSArray *)customBubblingEventTypes __deprecated_msg("Use RCTBubblingEventBlock props instead."); /** * DEPRECATED: declare properties of type RCTDirectEventBlock instead @@ -74,7 +74,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary *)customDirectEventTypes; +- (NSArray *)customDirectEventTypes __deprecated_msg("Use RCTDirectEventBlock props instead."); /** * Called to notify manager that layout has finished, in case any calculated