diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 8a238edaebf735..cebbb1f0c10726 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -75,6 +75,12 @@ type IOSProps = $ReadOnly<{| * @platform ios */ automaticallyAdjustContentInsets?: ?boolean, + /** + * Set to false if the scroll should be clamped to the bounds of the + * ScrollView children when using the scrollTo() function. The default value is true. + * @platform ios + */ + allowScrollOutOfBounds?: ?boolean, /** * The amount by which the scroll view content is inset from the edges * of the scroll view. Defaults to `{top: 0, left: 0, bottom: 0, right: 0}`. @@ -976,6 +982,10 @@ class ScrollView extends React.Component { : styles.baseVertical; const props = { ...this.props, + allowScrollOutOfBounds: + this.props.allowScrollOutOfBounds !== undefined + ? this.props.allowScrollOutOfBounds + : true, alwaysBounceHorizontal, alwaysBounceVertical, style: ([baseStyle, this.props.style]: ?Array), diff --git a/React/Views/ScrollView/RCTScrollView.h b/React/Views/ScrollView/RCTScrollView.h index 3404422c7b00da..74af0678ab74b7 100644 --- a/React/Views/ScrollView/RCTScrollView.h +++ b/React/Views/ScrollView/RCTScrollView.h @@ -38,6 +38,7 @@ */ @property (nonatomic, readonly) UIScrollView *scrollView; +@property (nonatomic, assign) BOOL allowScrollOutOfBounds; @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, assign) BOOL DEPRECATED_sendUpdatedChildFrames; diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index 58bff0f0fd2c25..b96896009a3455 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -585,12 +585,40 @@ - (void)scrollToOffset:(CGPoint)offset [self scrollToOffset:offset animated:YES]; } +- (CGPoint)getMaxXOffset +{ + CGFloat offsetX = _scrollView.contentSize.width - _scrollView.bounds.size.width + _scrollView.contentInset.right; + return CGPointMake(fmax(offsetX, 0), 0); +} + +- (CGPoint)getMaxYOffset +{ + CGFloat offsetY = _scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.contentInset.bottom; + return CGPointMake(0, fmax(offsetY, 0)); +} + +- (CGPoint)getMaxOffset +{ + return [self isHorizontal:_scrollView] ? [self getMaxXOffset] : [self getMaxYOffset]; +} + - (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated { - if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) { + CGPoint maxX = [self getMaxXOffset]; + CGPoint maxY = [self getMaxYOffset]; + CGPoint toOffset; + + if (self.allowScrollOutOfBounds) { + toOffset = offset; + } else { + // Restrict inside the scroll view container bounds + toOffset = CGPointMake(fmax(0, fmin(offset.x, maxX.x)), fmax(0, fmin(offset.y, maxY.y))); + } + + if (!CGPointEqualToPoint(_scrollView.contentOffset, toOffset)) { // Ensure at least one scroll event will fire _allowNextScrollNoMatterWhat = YES; - [_scrollView setContentOffset:offset animated:animated]; + [_scrollView setContentOffset:toOffset animated:animated]; } } @@ -600,15 +628,7 @@ - (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated */ - (void)scrollToEnd:(BOOL)animated { - BOOL isHorizontal = [self isHorizontal:_scrollView]; - CGPoint offset; - if (isHorizontal) { - CGFloat offsetX = _scrollView.contentSize.width - _scrollView.bounds.size.width + _scrollView.contentInset.right; - offset = CGPointMake(fmax(offsetX, 0), 0); - } else { - CGFloat offsetY = _scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.contentInset.bottom; - offset = CGPointMake(0, fmax(offsetY, 0)); - } + CGPoint offset = [self getMaxOffset]; if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) { // Ensure at least one scroll event will fire _allowNextScrollNoMatterWhat = YES; diff --git a/React/Views/ScrollView/RCTScrollViewManager.m b/React/Views/ScrollView/RCTScrollViewManager.m index 6494d52f48fb29..f6fd69093f6290 100644 --- a/React/Views/ScrollView/RCTScrollViewManager.m +++ b/React/Views/ScrollView/RCTScrollViewManager.m @@ -54,6 +54,7 @@ - (UIView *)view return [[RCTScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; } +RCT_EXPORT_VIEW_PROPERTY(allowScrollOutOfBounds, BOOL) RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal, BOOL) RCT_EXPORT_VIEW_PROPERTY(alwaysBounceVertical, BOOL) RCT_EXPORT_VIEW_PROPERTY(bounces, BOOL)