Skip to content

Commit db69948

Browse files
committed
Wait for the modal to open
...both in `PickerAvoidingView` and with scrolling to the picker. This is to prevent jiggering.
1 parent c89994c commit db69948

File tree

3 files changed

+42
-12
lines changed

3 files changed

+42
-12
lines changed

src/PickerAvoidingView/index.ios.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { StyleSheet, View } from 'react-native';
3-
import { PickerStateContext } from "../PickerStateProvider";
4-
import { IOS_MODAL_HEIGHT } from "../constants";
3+
import { PickerStateContext } from '../PickerStateProvider';
4+
import { IOS_MODAL_ANIMATION_DURATION_MS, IOS_MODAL_HEIGHT } from '../constants';
55

66
const styles = {
77
container: {
@@ -17,6 +17,11 @@ const styles = {
1717
},
1818
};
1919

20+
function schedule(callback, timeout) {
21+
const handle = setTimeout(callback, timeout);
22+
return () => clearTimeout(handle);
23+
}
24+
2025
/**
2126
* PickerAvoidingView is a React component that adjusts the view layout to avoid
2227
* being covered by an open iOS UIPickerView modal. It's meant to be similar to
@@ -33,12 +38,22 @@ export function PickerAvoidingView(props) {
3338
const context = React.useContext(PickerStateContext);
3439
const isPickerOpen = context && context.isPickerOpen;
3540

41+
const [shouldAddSpace, setShouldAddSpace] = React.useState(false);
42+
43+
React.useEffect(() => {
44+
if (isPickerOpen) {
45+
return schedule(() => {
46+
setShouldAddSpace(true);
47+
}, IOS_MODAL_ANIMATION_DURATION_MS);
48+
} else {
49+
setShouldAddSpace(false);
50+
}
51+
}, [isPickerOpen]);
52+
3653
return (
3754
<View style={StyleSheet.compose(props.style, styles.container)}>
38-
<View style={styles.contentContainer}>
39-
{props.children}
40-
</View>
41-
{isPickerOpen && <View style={styles.space}/>}
55+
<View style={styles.contentContainer}>{props.children}</View>
56+
{shouldAddSpace && <View style={styles.space} />}
4257
</View>
4358
);
4459
}

src/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
// Measuring the modal before rendering is not working reliably, so we need to hardcode the height
22
// This height was tested thoroughly on several iPhone models (iPhone SE, from iPhone 8 to 14 Pro, and 14 Pro Max)
33
export const IOS_MODAL_HEIGHT = 262;
4+
5+
// An approximated duration of the modal opening
6+
export const IOS_MODAL_ANIMATION_DURATION_MS = 500;

src/index.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import React, { PureComponent } from 'react';
2-
import { Keyboard, Modal, Platform, Text, TextInput, TouchableOpacity, View } from 'react-native';
2+
import {
3+
Dimensions,
4+
Keyboard,
5+
Modal,
6+
Platform,
7+
Text,
8+
TextInput,
9+
TouchableOpacity,
10+
View,
11+
} from 'react-native';
312
import PropTypes from 'prop-types';
413
import isEqual from 'lodash.isequal';
514
import { Picker } from '@react-native-picker/picker';
615
import { defaultStyles } from './styles';
7-
import { Dimensions } from 'react-native';
816
import { PickerAvoidingView } from './PickerAvoidingView';
917
import { PickerStateContext, PickerStateProvider } from './PickerStateProvider';
10-
import { IOS_MODAL_HEIGHT } from './constants';
18+
import { IOS_MODAL_ANIMATION_DURATION_MS, IOS_MODAL_HEIGHT } from './constants';
1119

1220
export default class RNPickerSelect extends PureComponent {
1321
static contextType = PickerStateContext;
@@ -240,9 +248,13 @@ export default class RNPickerSelect extends PureComponent {
240248

241249
// If TextInput is below picker modal, scroll up
242250
if (textInputBottomY > modalY) {
243-
this.props.scrollViewRef.current.scrollTo({
244-
y: textInputBottomY - modalY + this.props.scrollViewContentOffsetY,
245-
});
251+
// Wait until the modal animation finishes, so the scrolling animation doesn't run at the same time as
252+
// the modal animation. This prevents jiggering if PickerAvoidingView is used.
253+
setTimeout(() => {
254+
this.props.scrollViewRef.current.scrollTo({
255+
y: textInputBottomY - modalY + 10 + this.props.scrollViewContentOffsetY,
256+
});
257+
}, IOS_MODAL_ANIMATION_DURATION_MS + 50);
246258
}
247259
});
248260
}

0 commit comments

Comments
 (0)