Skip to content

Commit 65184ec

Browse files
sahrensfacebook-github-bot
authored andcommitted
rename and extend new maintain visible content position feature
Summary: Builds off of cae7179 - Make the prop a dictionary for more configuration options - Rename `maintainPositionAtOrBeyondIndex` -> `maintainVisibleContentPosition` + `minIndexForVisible` - Add autoscroll threshold feature Given the async native of RN JS and background layout, there is no way to trigger the scrollTo from JS without risking a delay, so we add the feature in native code. == Test Plan == ScrollViewExample: https://youtu.be/pmY8pxC9PRs Reviewed By: shergin Differential Revision: D6729160 fbshipit-source-id: 70f9bae460ce84567857a4f696da78ce9b3b834c
1 parent 7e7d00a commit 65184ec

File tree

5 files changed

+56
-22
lines changed

5 files changed

+56
-22
lines changed

Libraries/Components/ScrollView/ScrollView.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -234,18 +234,33 @@ const ScrollView = createReactClass({
234234
*/
235235
keyboardShouldPersistTaps: PropTypes.oneOf(['always', 'never', 'handled', false, true]),
236236
/**
237-
* When non-null, the scroll view will adjust the scroll position so that the content at or
238-
* beyond the specified index that is currently visible will not change position. This is useful
239-
* for lists that are loading content in both directions, e.g. a chat thread, where new messages
240-
* coming in might otherwise cause the scroll position to jump. A value of 1 can be used to skip
241-
* a spinner that does not need to maintain position. The default value is null.
237+
* When set, the scroll view will adjust the scroll position so that the first child that is
238+
* currently visible and at or beyond `minIndexForVisible` will not change position. This is
239+
* useful for lists that are loading content in both directions, e.g. a chat thread, where new
240+
* messages coming in might otherwise cause the scroll position to jump. A value of 0 is common,
241+
* but other values such as 1 can be used to skip loading spinners or other content that should
242+
* not maintain position.
242243
*
243-
* Caveat: reordering elements in the scrollview with this enabled will probably cause jumpiness
244-
* and jank. It can be fixed, but there are currently no plans to do so.
244+
* The optional `autoscrollToTopThreshold` can be used to make the content automatically scroll
245+
* to the top after making the adjustment if the user was within the threshold of the top before
246+
* the adjustment was made. This is also useful for chat-like applications where you want to see
247+
* new messages scroll into place, but not if the user has scrolled up a ways and it would be
248+
* disruptive to scroll a bunch.
249+
*
250+
* Caveat 1: Reordering elements in the scrollview with this enabled will probably cause
251+
* jumpiness and jank. It can be fixed, but there are currently no plans to do so. For now,
252+
* don't re-order the content of any ScrollViews or Lists that use this feature.
253+
*
254+
* Caveat 2: This simply uses `contentOffset` and `frame.origin` in native code to compute
255+
* visibility. Occlusion, transforms, and other complexity won't be taken into account as to
256+
* whether content is "visible" or not.
245257
*
246258
* @platform ios
247259
*/
248-
maintainPositionAtOrBeyondIndex: PropTypes.number,
260+
maintainVisibleContentPosition: PropTypes.shape({
261+
minIndexForVisible: PropTypes.number.isRequired,
262+
autoscrollToTopThreshold: PropTypes.number,
263+
}),
249264
/**
250265
* The maximum allowed zoom scale. The default value is 1.0.
251266
* @platform ios

RNTester/js/ScrollViewExample.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ if (Platform.OS === 'ios') {
131131
exports.examples.push({
132132
title: '<ScrollView> smooth bi-directional content loading\n',
133133
description:
134-
'The `maintainPositionAtOrBeyondIndex` prop allows insertions to either end of the content ' +
134+
'The `maintainVisibleContentPosition` prop allows insertions to either end of the content ' +
135135
'without causing the visible content to jump. Re-ordering is not supported.',
136136
render: function() {
137137
let itemCount = 6;
@@ -146,7 +146,10 @@ if (Platform.OS === 'ios') {
146146
<View>
147147
<ScrollView
148148
automaticallyAdjustContentInsets={false}
149-
maintainPositionAtOrBeyondIndex={1}
149+
maintainVisibleContentPosition={{
150+
minIndexForVisible: 1,
151+
autoscrollToTopThreshold: 10,
152+
}}
150153
style={styles.scrollView}>
151154
<ActivityIndicator style={{height: 40}} />
152155
{this.state.items.map(item =>
@@ -156,9 +159,12 @@ if (Platform.OS === 'ios') {
156159
<ScrollView
157160
horizontal={true}
158161
automaticallyAdjustContentInsets={false}
159-
maintainPositionAtOrBeyondIndex={1}
162+
maintainVisibleContentPosition={{
163+
minIndexForVisible: 1,
164+
autoscrollToTopThreshold: 10,
165+
}}
160166
style={[styles.scrollView, styles.horizontalScrollView]}>
161-
<ActivityIndicator style={{height: 40}} />
167+
<ActivityIndicator style={{width: 40}} />
162168
{this.state.items.map(item =>
163169
React.cloneElement(item, {key: item.props.msg, style: null}),
164170
)}

React/Views/ScrollView/RCTScrollView.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
@property (nonatomic, assign) BOOL DEPRECATED_sendUpdatedChildFrames;
4646
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
4747
@property (nonatomic, assign) BOOL centerContent;
48-
@property (nonatomic, copy) NSNumber *maintainPositionAtOrBeyondIndex;
48+
@property (nonatomic, copy) NSDictionary *maintainVisibleContentPosition;
4949
@property (nonatomic, assign) int snapToInterval;
5050
@property (nonatomic, copy) NSString *snapToAlignment;
5151

React/Views/ScrollView/RCTScrollView.m

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -911,16 +911,16 @@ - (void)updateContentOffsetIfNeeded
911911
}
912912
}
913913

914-
// maintainPositionAtOrBeyondIndex is used to allow seamless loading of content from both ends of
914+
// maintainVisibleContentPosition is used to allow seamless loading of content from both ends of
915915
// the scrollview without the visible content jumping in position.
916-
- (void)setMaintainPositionAtOrBeyondIndex:(NSNumber *)maintainPositionAtOrBeyondIndex
916+
- (void)setMaintainVisibleContentPosition:(NSDictionary *)maintainVisibleContentPosition
917917
{
918-
if (maintainPositionAtOrBeyondIndex != nil) {
918+
if (maintainVisibleContentPosition != nil && _maintainVisibleContentPosition == nil) {
919919
[_eventDispatcher.bridge.uiManager.observerCoordinator addObserver:self];
920-
} else {
920+
} else if (maintainVisibleContentPosition == nil && _maintainVisibleContentPosition != nil) {
921921
[_eventDispatcher.bridge.uiManager.observerCoordinator removeObserver:self];
922922
}
923-
_maintainPositionAtOrBeyondIndex = maintainPositionAtOrBeyondIndex;
923+
_maintainVisibleContentPosition = maintainVisibleContentPosition;
924924
}
925925

926926
#pragma mark - RCTUIManagerObserver
@@ -930,7 +930,7 @@ - (void)uiManagerWillPerformMounting:(RCTUIManager *)manager
930930
RCTAssertUIManagerQueue();
931931
[manager prependUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
932932
BOOL horz = [self isHorizontal:self->_scrollView];
933-
NSUInteger minIdx = [self->_maintainPositionAtOrBeyondIndex integerValue];
933+
NSUInteger minIdx = [self->_maintainVisibleContentPosition[@"minIndexForVisible"] integerValue];
934934
for (NSUInteger ii = minIdx; ii < self->_contentView.subviews.count; ++ii) {
935935
// Find the first entirely visible view. This must be done after we update the content offset
936936
// or it will tend to grab rows that were made visible by the shift in position
@@ -946,9 +946,10 @@ - (void)uiManagerWillPerformMounting:(RCTUIManager *)manager
946946
}
947947
}];
948948
[manager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
949-
if (self->_maintainPositionAtOrBeyondIndex == nil) {
949+
if (self->_maintainVisibleContentPosition == nil) {
950950
return; // The prop might have changed in the previous UIBlocks, so need to abort here.
951951
}
952+
NSNumber *autoscrollThreshold = self->_maintainVisibleContentPosition[@"autoscrollToTopThreshold"];
952953
// TODO: detect and handle/ignore re-ordering
953954
if ([self isHorizontal:self->_scrollView]) {
954955
CGFloat deltaX = self->_firstVisibleView.frame.origin.x - self->_prevFirstVisibleFrame.origin.x;
@@ -957,15 +958,27 @@ - (void)uiManagerWillPerformMounting:(RCTUIManager *)manager
957958
self->_scrollView.contentOffset.x + deltaX,
958959
self->_scrollView.contentOffset.y
959960
);
961+
if (autoscrollThreshold != nil) {
962+
// If the offset WAS within the threshold of the start, animate to the start.
963+
if (self->_scrollView.contentOffset.x - deltaX <= [autoscrollThreshold integerValue]) {
964+
[self scrollToOffset:CGPointMake(0, self->_scrollView.contentOffset.y) animated:YES];
965+
}
966+
}
960967
}
961968
} else {
962969
CGRect newFrame = self->_firstVisibleView.frame;
963970
CGFloat deltaY = newFrame.origin.y - self->_prevFirstVisibleFrame.origin.y;
964-
if (ABS(deltaY) > 0.1 || deltaY != 0.0) {
971+
if (ABS(deltaY) > 0.1) {
965972
self->_scrollView.contentOffset = CGPointMake(
966973
self->_scrollView.contentOffset.x,
967974
self->_scrollView.contentOffset.y + deltaY
968975
);
976+
if (autoscrollThreshold != nil) {
977+
// If the offset WAS within the threshold of the start, animate to the start.
978+
if (self->_scrollView.contentOffset.y - deltaY <= [autoscrollThreshold integerValue]) {
979+
[self scrollToOffset:CGPointMake(self->_scrollView.contentOffset.x, 0) animated:YES];
980+
}
981+
}
969982
}
970983
}
971984
}];

React/Views/ScrollView/RCTScrollViewManager.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ - (UIView *)view
6262
RCT_EXPORT_VIEW_PROPERTY(bouncesZoom, BOOL)
6363
RCT_EXPORT_VIEW_PROPERTY(canCancelContentTouches, BOOL)
6464
RCT_EXPORT_VIEW_PROPERTY(centerContent, BOOL)
65-
RCT_EXPORT_VIEW_PROPERTY(maintainPositionAtOrBeyondIndex, NSNumber)
65+
RCT_EXPORT_VIEW_PROPERTY(maintainVisibleContentPosition, NSDictionary)
6666
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
6767
RCT_EXPORT_VIEW_PROPERTY(decelerationRate, CGFloat)
6868
RCT_EXPORT_VIEW_PROPERTY(directionalLockEnabled, BOOL)

0 commit comments

Comments
 (0)