Skip to content

Commit bc1e602

Browse files
justinwhfacebook-github-bot
authored andcommitted
Add ScrollView.automaticallyAdjustsScrollIndicatorInsets prop (on iOS) (#29809)
Summary: iOS 13 added a new property to `UIScrollView`: `automaticallyAdjustsScrollIndicatorInsets`, which is `YES` by default. The property changes the meaning of the `scrollIndicatorInsets` property. When `YES`, any such insets are **in addition to** whatever insets would be applied by the device's safe area. When `NO`, the iOS <13 behavior is restored, which is for such insets to not account for safe area. In other words, this effects ScrollViews that underlay the device's safe area (i.e. under the notch). When `YES`, the OS "automatically" insets the scroll indicators, when `NO` it does not. There are two problems with the default `YES` setting: 1. It means applying `scrollIndicatorInsets` to a `ScrollView` has a different effect on iOS 13 versus iOS 12. 2. It limits developers' control over `scrollIndicatorInsets`. Since negative insets are not supported, if the insets the OS chooses are too large for your app, you cannot fix it. Further explanation & sample code is available in issue #28140 . This change sets the default for this property to `NO`, making the behavior consistent across iOS versions, and allowing developers full control. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Changed] - ScrollView scrollIndicatorInsets to not automatically add safe area on iOS13+ Pull Request resolved: #29809 Test Plan: Updated the RNTester example to explain what to expect. Also removed the `pageScreen` modal example for now as mentioned in my Github comment. {F628636466} Here are screenshots of the demo app (from the original bug) before (with safe area applied to insets) & after (without safe area applied to insets): ![before](https://user-images.githubusercontent.com/428831/91644197-ea03a700-ea07-11ea-9489-be27820930eb.png) ![after](https://user-images.githubusercontent.com/428831/91644200-eff98800-ea07-11ea-8788-daf1e783639d.png) Reviewed By: p-sun Differential Revision: D28229603 Pulled By: lunaleaps fbshipit-source-id: 2e774ae150b1dc41680b8b7886c7ceac8808136a
1 parent 4ad4426 commit bc1e602

File tree

11 files changed

+158
-0
lines changed

11 files changed

+158
-0
lines changed

Libraries/Components/ScrollView/ScrollView.js

+6
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ type IOSProps = $ReadOnly<{|
171171
* @platform ios
172172
*/
173173
automaticallyAdjustContentInsets?: ?boolean,
174+
/**
175+
* Controls whether iOS should automatically adjust the scroll indicator
176+
* insets. The default value is true. Available on iOS 13 and later.
177+
* @platform ios
178+
*/
179+
automaticallyAdjustsScrollIndicatorInsets?: ?boolean,
174180
/**
175181
* The amount by which the scroll view content is inset from the edges
176182
* of the scroll view. Defaults to `{top: 0, left: 0, bottom: 0, right: 0}`.

Libraries/Components/ScrollView/ScrollViewNativeComponent.js

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const ScrollViewNativeComponent: HostComponent<Props> = NativeComponentRegistry.
2626
alwaysBounceHorizontal: true,
2727
alwaysBounceVertical: true,
2828
automaticallyAdjustContentInsets: true,
29+
automaticallyAdjustsScrollIndicatorInsets: true,
2930
bounces: true,
3031
bouncesZoom: true,
3132
canCancelContentTouches: true,

Libraries/Components/ScrollView/ScrollViewNativeComponentType.js

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type ScrollViewNativeProps = $ReadOnly<{
2121
alwaysBounceHorizontal?: ?boolean,
2222
alwaysBounceVertical?: ?boolean,
2323
automaticallyAdjustContentInsets?: ?boolean,
24+
automaticallyAdjustsScrollIndicatorInsets?: ?boolean,
2425
bounces?: ?boolean,
2526
bouncesZoom?: ?boolean,
2627
canCancelContentTouches?: ?boolean,

Libraries/Components/ScrollView/ScrollViewViewConfig.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const ScrollViewViewConfig = {
2424
alwaysBounceHorizontal: true,
2525
alwaysBounceVertical: true,
2626
automaticallyAdjustContentInsets: true,
27+
automaticallyAdjustsScrollIndicatorInsets: true,
2728
bounces: true,
2829
bouncesZoom: true,
2930
canCancelContentTouches: true,

React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm

+8
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,14 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
251251
scrollView.snapToOffsets = snapToOffsets;
252252
}
253253

254+
if (@available(iOS 13.0, *)) {
255+
if (oldScrollViewProps.automaticallyAdjustsScrollIndicatorInsets !=
256+
newScrollViewProps.automaticallyAdjustsScrollIndicatorInsets) {
257+
scrollView.automaticallyAdjustsScrollIndicatorInsets =
258+
newScrollViewProps.automaticallyAdjustsScrollIndicatorInsets;
259+
}
260+
}
261+
254262
if (@available(iOS 11.0, *)) {
255263
if (oldScrollViewProps.contentInsetAdjustmentBehavior != newScrollViewProps.contentInsetAdjustmentBehavior) {
256264
auto const contentInsetAdjustmentBehavior = newScrollViewProps.contentInsetAdjustmentBehavior;

React/Views/ScrollView/RCTScrollView.m

+12
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,18 @@ -(type)getter \
928928
RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat);
929929
RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, scrollIndicatorInsets, UIEdgeInsets);
930930

931+
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
932+
- (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjusts API_AVAILABLE(ios(13.0))
933+
{
934+
// `automaticallyAdjustsScrollIndicatorInsets` is available since iOS 13.
935+
if ([_scrollView respondsToSelector:@selector(setAutomaticallyAdjustsScrollIndicatorInsets:)]) {
936+
if (@available(iOS 13.0, *)) {
937+
_scrollView.automaticallyAdjustsScrollIndicatorInsets = automaticallyAdjusts;
938+
}
939+
}
940+
}
941+
#endif
942+
931943
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
932944
- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior API_AVAILABLE(ios(11.0))
933945
{

React/Views/ScrollView/RCTScrollViewManager.m

+3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ - (UIView *)view
102102
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollBegin, RCTDirectEventBlock)
103103
RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock)
104104
RCT_EXPORT_VIEW_PROPERTY(inverted, BOOL)
105+
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
106+
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustsScrollIndicatorInsets, BOOL)
107+
#endif
105108
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
106109
RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior)
107110
#endif

ReactCommon/react/renderer/components/scrollview/ScrollViewProps.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ ScrollViewProps::ScrollViewProps(
5151
"automaticallyAdjustContentInsets",
5252
sourceProps.automaticallyAdjustContentInsets,
5353
{})),
54+
automaticallyAdjustsScrollIndicatorInsets(convertRawProp(
55+
rawProps,
56+
"automaticallyAdjustsScrollIndicatorInsets",
57+
sourceProps.automaticallyAdjustsScrollIndicatorInsets,
58+
true)),
5459
decelerationRate(convertRawProp(
5560
rawProps,
5661
"decelerationRate",
@@ -206,6 +211,10 @@ SharedDebugStringConvertibleList ScrollViewProps::getDebugProps() const {
206211
"automaticallyAdjustContentInsets",
207212
automaticallyAdjustContentInsets,
208213
defaultScrollViewProps.automaticallyAdjustContentInsets),
214+
debugStringConvertibleItem(
215+
"automaticallyAdjustsScrollIndicatorInsets",
216+
automaticallyAdjustsScrollIndicatorInsets,
217+
defaultScrollViewProps.automaticallyAdjustsScrollIndicatorInsets),
209218
debugStringConvertibleItem(
210219
"decelerationRate",
211220
decelerationRate,

ReactCommon/react/renderer/components/scrollview/ScrollViewProps.h

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class ScrollViewProps final : public ViewProps {
2828
bool canCancelContentTouches{true};
2929
bool centerContent{};
3030
bool automaticallyAdjustContentInsets{};
31+
bool automaticallyAdjustsScrollIndicatorInsets{true};
3132
Float decelerationRate{0.998f};
3233
bool directionalLockEnabled{};
3334
ScrollViewIndicatorStyle indicatorStyle{};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
import * as React from 'react';
11+
12+
import {
13+
Button,
14+
Modal,
15+
ScrollView,
16+
StyleSheet,
17+
Switch,
18+
Text,
19+
View,
20+
useWindowDimensions,
21+
} from 'react-native';
22+
23+
export function ScrollViewIndicatorInsetsExample() {
24+
const [automaticallyAdjustsScrollIndicatorInsets, setAutomaticallyAdjustsScrollIndicatorInsets] = React.useState(true);
25+
const [modalVisible, setModalVisible] = React.useState(false);
26+
const { height, width } = useWindowDimensions();
27+
28+
return (
29+
<View>
30+
<Modal
31+
animationType="slide"
32+
visible={modalVisible}
33+
onRequestClose={() => setModalVisible(false)}
34+
presentationStyle="fullScreen"
35+
statusBarTranslucent={false}
36+
supportedOrientations={['portrait', 'landscape']}>
37+
<View style={styles.modal}>
38+
<ScrollView
39+
contentContainerStyle={[
40+
styles.scrollViewContent,
41+
{
42+
height: (height * 1.2),
43+
width: (width * 1.2),
44+
},
45+
]}
46+
automaticallyAdjustsScrollIndicatorInsets={automaticallyAdjustsScrollIndicatorInsets}
47+
style={styles.scrollView}>
48+
<View style={styles.description}>
49+
<Text>When <Text style={styles.code}>automaticallyAdjustsScrollIndicatorInsets</Text> is true, the scrollbar is inset to the status bar. When false, it reaches the edge of the modal.</Text>
50+
<Text>Check out the UIScrollView docs to learn more about <Text style={styles.code}>automaticallyAdjustsScrollIndicatorInsets</Text></Text>
51+
</View>
52+
<View style={styles.toggle}>
53+
<Text><Text style={styles.code}>automaticallyAdjustsScrollIndicatorInsets</Text> is {automaticallyAdjustsScrollIndicatorInsets + ''}</Text>
54+
<Switch
55+
onValueChange={v => setAutomaticallyAdjustsScrollIndicatorInsets(v)}
56+
value={automaticallyAdjustsScrollIndicatorInsets}
57+
style={styles.switch}/>
58+
</View>
59+
<Button
60+
onPress={() => setModalVisible(false)}
61+
title="Close"/>
62+
</ScrollView>
63+
</View>
64+
</Modal>
65+
<Text />
66+
<Button
67+
onPress={() => setModalVisible(true)}
68+
title="Present Fullscreen Modal with ScrollView"/>
69+
</View>
70+
);
71+
}
72+
73+
const styles = StyleSheet.create({
74+
modal: {
75+
flex: 1,
76+
},
77+
scrollView: {
78+
flex: 1,
79+
height: 1000,
80+
},
81+
scrollViewContent: {
82+
alignItems: 'center',
83+
backgroundColor: '#ffaaaa',
84+
justifyContent: 'flex-start',
85+
paddingTop: 200,
86+
},
87+
switch: {
88+
marginBottom: 40,
89+
},
90+
toggle:{
91+
margin: 20,
92+
alignItems: 'center',
93+
},
94+
description: {
95+
marginHorizontal: 80,
96+
},
97+
code: {
98+
fontSize: 10,
99+
fontFamily: 'Courier',
100+
},
101+
});
102+
103+
exports.title = 'ScrollViewIndicatorInsets';
104+
exports.category = 'iOS';
105+
exports.description =
106+
'ScrollView automaticallyAdjustsScrollIndicatorInsets adjusts scroll indicator insets using OS-defined logic on iOS 13+.';
107+
exports.examples = [
108+
{
109+
title: '<ScrollView> automaticallyAdjustsScrollIndicatorInsets Example',
110+
render: (): React.Node => <ScrollViewIndicatorInsetsExample/>,
111+
},
112+
];

packages/rn-tester/js/utils/RNTesterList.ios.js

+4
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ const Components: Array<RNTesterModuleInfo> = [
118118
module: require('../examples/ScrollView/ScrollViewAnimatedExample'),
119119
supportsTVOS: true,
120120
},
121+
{
122+
key: 'ScrollViewIndicatorInsetsExample',
123+
module: require('../examples/ScrollView/ScrollViewIndicatorInsetsExample'),
124+
},
121125
{
122126
key: 'SectionListExample',
123127
module: require('../examples/SectionList/SectionListIndex'),

0 commit comments

Comments
 (0)