From 3e41e1707dceedf7aa68781ed752ade8f3a4119a Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Tue, 22 Aug 2017 13:49:20 -0700 Subject: [PATCH 1/2] iOS ScrollView: Add scrollBy method Currently, React Native allows you to scroll a ScrollView to a particular position using `scrollTo`. If instead you want to scroll by a delta, you could try to do this using `scrollTo` (add the delta to the current scroll position) but this doesn't always work out as intended due to the async nature of React Native (what you think is the current scroll position might be a stale value). This change introduces a `scrollBy` API to solve this problem. `scrollBy` takes a delta to scroll by and native takes care of doing the arithmetic because it knows the latest scroll position. --- Libraries/Components/ScrollResponder.js | 15 +++++++++++++++ Libraries/Components/ScrollView/ScrollView.js | 13 +++++++++++++ React/Views/RCTScrollView.m | 11 +++++++++++ React/Views/RCTScrollViewManager.m | 17 +++++++++++++++++ React/Views/RCTScrollableProtocol.h | 1 + 5 files changed, 57 insertions(+) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 36f714228042..c7db4caebfad 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -438,6 +438,21 @@ var ScrollResponderMixin = { ); }, + /** + * A helper function to scroll by a specific offset in the ScrollView. + * This is useful when you want to change the size of the content view and scroll + * position at the same time. Syntax: + * + * scrollResponderScrollBy(options: {deltaX: number = 0; deltaY: number = 0; animated: boolean = true}) + */ + scrollResponderScrollBy: function(options : {deltaX?: number, deltaY?: number, animated?: boolean}) { + UIManager.dispatchViewManagerCommand( + this.scrollResponderGetScrollableNode(), + UIManager.RCTScrollView.Commands.scrollBy, + [options.deltaX || 0, options.deltaY || 0, options.animated !== false], + ); + }, + /** * Deprecated, do not use. */ diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 1787476babc0..cd95775a59fa 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -530,6 +530,19 @@ const ScrollView = createReactClass({ }); }, + /** + * Scrolls by a given offset, either immediately or with a smooth animation. + * + * Syntax: + * + * `scrollBy(options: {deltaX: number = 0; deltaY: number = 0; animated: boolean = true})` + */ + scrollBy: function(options: { deltaX?: number, deltaY?: number, animated?: boolean } ) { + const data = {deltaX: options.deltaX || 0, deltaY: options.deltaY || 0, + animated: options.animated !== false}; + this.getScrollResponder().scrollResponderScrollBy(data); + }, + /** * Deprecated, use `scrollTo` instead. */ diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 18e5546d1e96..4fe76507d34a 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -587,6 +587,17 @@ - (void)scrollToEnd:(BOOL)animated } } +- (void)scrollByOffset:(CGPoint)offset animated:(BOOL)animated +{ + if (offset.x != 0 || offset.y != 0) { + // Ensure at least one scroll event will fire + _allowNextScrollNoMatterWhat = YES; + CGPoint oldOffset = _scrollView.contentOffset; + CGPoint newOffset = (CGPoint){oldOffset.x + offset.x, oldOffset.y + offset.y}; + [_scrollView setContentOffset:newOffset animated:animated]; + } +} + - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated { [_scrollView zoomToRect:rect animated:animated]; diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index efa162bc77e0..32b927f812cb 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -175,6 +175,23 @@ - (UIView *)view }]; } +RCT_EXPORT_METHOD(scrollBy:(nonnull NSNumber *)reactTag + deltaX:(CGFloat)deltaX + deltaY:(CGFloat)deltaY + animated:(BOOL)animated) +{ + [self.bridge.uiManager addUIBlock: + ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ + UIView *view = viewRegistry[reactTag]; + if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { + [(id)view scrollByOffset:(CGPoint){deltaX, deltaY} animated:animated]; + } else { + RCTLogError(@"tried to scrollBy: on non-RCTScrollableProtocol view %@ " + "with tag #%@", view, reactTag); + } + }]; +} + RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag withRect:(CGRect)rect animated:(BOOL)animated) diff --git a/React/Views/RCTScrollableProtocol.h b/React/Views/RCTScrollableProtocol.h index a143d638602f..a3e3ea8042ec 100644 --- a/React/Views/RCTScrollableProtocol.h +++ b/React/Views/RCTScrollableProtocol.h @@ -19,6 +19,7 @@ - (void)scrollToOffset:(CGPoint)offset; - (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated; +- (void)scrollByOffset:(CGPoint)offset animated:(BOOL)animated; /** * If this is a vertical scroll view, scrolls to the bottom. * If this is a horizontal scroll view, scrolls to the right. From b27beda7f1df860eac413aa0d862f4159c7e1da6 Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Tue, 22 Aug 2017 15:18:06 -0700 Subject: [PATCH 2/2] Remove trailing whitespace --- Libraries/Components/ScrollResponder.js | 2 +- Libraries/Components/ScrollView/ScrollView.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index c7db4caebfad..88c098fb4462 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -440,7 +440,7 @@ var ScrollResponderMixin = { /** * A helper function to scroll by a specific offset in the ScrollView. - * This is useful when you want to change the size of the content view and scroll + * This is useful when you want to change the size of the content view and scroll * position at the same time. Syntax: * * scrollResponderScrollBy(options: {deltaX: number = 0; deltaY: number = 0; animated: boolean = true}) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index cd95775a59fa..dd2ceffb0f09 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -538,7 +538,7 @@ const ScrollView = createReactClass({ * `scrollBy(options: {deltaX: number = 0; deltaY: number = 0; animated: boolean = true})` */ scrollBy: function(options: { deltaX?: number, deltaY?: number, animated?: boolean } ) { - const data = {deltaX: options.deltaX || 0, deltaY: options.deltaY || 0, + const data = {deltaX: options.deltaX || 0, deltaY: options.deltaY || 0, animated: options.animated !== false}; this.getScrollResponder().scrollResponderScrollBy(data); },