diff --git a/Examples/UIExplorer/js/ScrollViewExample.js b/Examples/UIExplorer/js/ScrollViewExample.js
index 7eb41692070..4d5dc6bcf90 100644
--- a/Examples/UIExplorer/js/ScrollViewExample.js
+++ b/Examples/UIExplorer/js/ScrollViewExample.js
@@ -57,6 +57,11 @@ exports.examples = [
onPress={() => { _scrollView.scrollTo({y: 0}); }}>
Scroll to top
+ { _scrollView.scrollToEnd({animated: true}); }}>
+ Scroll to bottom
+
);
}
@@ -79,6 +84,11 @@ exports.examples = [
onPress={() => { _scrollView.scrollTo({x: 0}); }}>
Scroll to start
+ { _scrollView.scrollToEnd({animated: true}); }}>
+ Scroll to end
+
);
}
diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js
index 5bd9d93f281..aec5a260ed2 100644
--- a/Libraries/Components/ScrollResponder.js
+++ b/Libraries/Components/ScrollResponder.js
@@ -377,11 +377,11 @@ var ScrollResponderMixin = {
},
/**
- * A helper function to scroll to a specific point in the scrollview.
- * This is currently used to help focus on child textviews, but can also
+ * A helper function to scroll to a specific point in the ScrollView.
+ * This is currently used to help focus child TextViews, but can also
* be used to quickly scroll to any element we want to focus. Syntax:
*
- * scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})
+ * `scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})`
*
* Note: The weird argument signature is due to the fact that, for historical reasons,
* the function also accepts separate arguments as as alternative to the options object.
@@ -404,6 +404,32 @@ var ScrollResponderMixin = {
);
},
+ /**
+ * Scrolls to the end of the ScrollView, either immediately or with a smooth
+ * animation.
+ *
+ * Example:
+ *
+ * `scrollResponderScrollToEnd({animated: true})`
+ */
+ scrollResponderScrollToEnd: function(
+ options?: { animated?: boolean },
+ ) {
+ if (Platform.OS !== 'ios') {
+ console.warn(
+ 'scrollResponderScrollToEnd is not supported on this platform'
+ );
+ return;
+ }
+ // Default to true
+ const animated = (options && options.animated) !== false;
+ UIManager.dispatchViewManagerCommand(
+ this.scrollResponderGetScrollableNode(),
+ UIManager.RCTScrollView.Commands.scrollToEnd,
+ [animated],
+ );
+ },
+
/**
* Deprecated, do not use.
*/
diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js
index a53ff84ec9f..04dfc3607a9 100644
--- a/Libraries/Components/ScrollView/ScrollView.js
+++ b/Libraries/Components/ScrollView/ScrollView.js
@@ -382,11 +382,11 @@ const ScrollView = React.createClass({
/**
* Scrolls to a given x, y offset, either immediately or with a smooth animation.
*
- * Syntax:
+ * Example:
*
- * `scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})`
+ * `scrollTo({x: 0; y: 0; animated: true})`
*
- * Note: The weird argument signature is due to the fact that, for historical reasons,
+ * Note: The weird function signature is due to the fact that, for historical reasons,
* the function also accepts separate arguments as as alternative to the options object.
* This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
*/
@@ -404,7 +404,39 @@ const ScrollView = React.createClass({
},
/**
- * Deprecated, do not use.
+ * If this is a vertical ScrollView scrolls to the bottom.
+ * If this is a horizontal ScrollView scrolls to the right.
+ *
+ * Use `scrollToEnd({animated: true})` for smooth animated scrolling,
+ * `scrollToEnd({animated: false})` for immediate scrolling.
+ * If no options are passed, `animated` defaults to true.
+ *
+ * See `ScrollView#scrollToEnd`.
+ */
+ scrollToEnd: function(
+ options?: { animated?: boolean },
+ ) {
+ // Default to true
+ const animated = (options && options.animated) !== false;
+ if (Platform.OS === 'ios') {
+ this.getScrollResponder().scrollResponderScrollToEnd({
+ animated: animated,
+ });
+ } else if (Platform.OS === 'android') {
+ // On Android scrolling past the end of the ScrollView gets clipped
+ // - scrolls to the end.
+ if (this.props.horizontal) {
+ this.scrollTo({x: 10*1000*1000, animated: animated});
+ } else {
+ this.scrollTo({y: 10*1000*1000, animated: animated});
+ }
+ } else {
+ console.warn('scrollToEnd is not supported on this platform');
+ }
+ },
+
+ /**
+ * Deprecated, use `scrollTo` instead.
*/
scrollWithoutAnimationTo: function(y: number = 0, x: number = 0) {
console.warn('`scrollWithoutAnimationTo` is deprecated. Use `scrollTo` instead');
diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js
index 81e5aadd2bb..0468fbe8158 100644
--- a/Libraries/CustomComponents/ListView/ListView.js
+++ b/Libraries/CustomComponents/ListView/ListView.js
@@ -287,6 +287,29 @@ var ListView = React.createClass({
}
},
+ /**
+ * If this is a vertical ListView scrolls to the bottom.
+ * If this is a horizontal ListView scrolls to the right.
+ *
+ * Use `scrollToEnd({animated: true})` for smooth animated scrolling,
+ * `scrollToEnd({animated: false})` for immediate scrolling.
+ * If no options are passed, `animated` defaults to true.
+ *
+ * See `ScrollView#scrollToEnd`.
+ */
+ scrollToEnd: function(options?: { animated?: boolean }) {
+ if (this._scrollComponent) {
+ if (this._scrollComponent.scrollToEnd) {
+ this._scrollComponent.scrollToEnd(options);
+ } else {
+ console.warn(
+ 'The scroll component used by the ListView does not support ' +
+ 'scrollToEnd. Check the renderScrollComponent prop of your ListView.'
+ );
+ }
+ }
+ },
+
setNativeProps: function(props: Object) {
if (this._scrollComponent) {
this._scrollComponent.setNativeProps(props);
diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m
index 5f5c9e2dc2b..aa85fa50c5b 100644
--- a/React/Base/RCTModuleMethod.m
+++ b/React/Base/RCTModuleMethod.m
@@ -480,10 +480,10 @@ SEL RCTParseMethodSignature(NSString *methodSignature, NSArray self.frame.size.width;
+}
+
- (void)scrollToOffset:(CGPoint)offset
{
[self scrollToOffset:offset animated:YES];
@@ -602,6 +607,26 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
}
}
+/**
+ * If this is a vertical scroll view, scrolls to the bottom.
+ * If this is a horizontal scroll view, scrolls to the right.
+ */
+- (void)scrollToEnd:(BOOL)animated
+{
+ BOOL isHorizontal = [self isHorizontal:_scrollView];
+ CGPoint offset;
+ if (isHorizontal) {
+ offset = CGPointMake(_scrollView.contentSize.width - _scrollView.bounds.size.width, 0);
+ } else {
+ offset = CGPointMake(0, _scrollView.contentSize.height - _scrollView.bounds.size.height);
+ }
+ if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) {
+ // Ensure at least one scroll event will fire
+ _allowNextScrollNoMatterWhat = YES;
+ [_scrollView setContentOffset:offset animated:animated];
+ }
+}
+
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[_scrollView zoomToRect:rect animated:animated];
@@ -727,7 +752,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll)
CGFloat snapToIntervalF = (CGFloat)self.snapToInterval;
// Find which axis to snap
- BOOL isHorizontal = (scrollView.contentSize.width > self.frame.size.width);
+ BOOL isHorizontal = [self isHorizontal:scrollView];
// What is the current offset?
CGFloat targetContentOffsetAlongAxis = isHorizontal ? targetContentOffset->x : targetContentOffset->y;
diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m
index 91f5634f01c..229450e76fa 100644
--- a/React/Views/RCTScrollViewManager.m
+++ b/React/Views/RCTScrollViewManager.m
@@ -147,6 +147,21 @@ RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag
}];
}
+RCT_EXPORT_METHOD(scrollToEnd:(nonnull NSNumber *)reactTag
+ animated:(BOOL)animated)
+{
+ [self.bridge.uiManager addUIBlock:
+ ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){
+ UIView *view = viewRegistry[reactTag];
+ if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
+ [(id)view scrollToEnd:animated];
+ } else {
+ RCTLogError(@"tried to scrollTo: 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 90c14ebd3cc..a143d638602 100644
--- a/React/Views/RCTScrollableProtocol.h
+++ b/React/Views/RCTScrollableProtocol.h
@@ -19,6 +19,11 @@
- (void)scrollToOffset:(CGPoint)offset;
- (void)scrollToOffset:(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.
+ */
+- (void)scrollToEnd:(BOOL)animated;
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;
- (void)addScrollListener:(NSObject *)scrollListener;