Reputation: 420
I am using expo-router with the (tabs) directory. I'm also using react-native-safe-area-context's SafeAreaView. I would like, in portrait orientation, the (tabs) nav bar to appear above the keyboard with the keyboard open on both iOS and Android, and to use a ScrollView in the components above that.
I've found that on Android I do not need a <KeyboardAvoidingView>
to preserve correct ScrollView behavior above the keyboard, but on iOS I do. So I place that in a TabWrapper
like so:
export const TabWrapper = (props: PropsWithChildren) => {
return (
<SafeAreaView style={{ flex: 1 }}>
{Platform.OS === "ios" ? (
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
{props.children}
</KeyboardAvoidingView>
) : (
<>{props.children}</>
)}
</SafeAreaView>
);
};
The PlayerTab
has a header and a form:
export const PlayerTab = ({ dir }: { dir: DirectionLetter }) => {
return (
<TabWrapper>
<PlayerHeader specifyingPlayerDir={dir} />
<FormAssignPlayer
direction={dir}
nextRoute={
dir === "N"
? "/PlayerW"
: dir === "W"
? "/PlayerE"
: dir === "E"
? "/PlayerS"
: "/PlayerConfirm"
}
/>
</TabWrapper>
);
};
The form is what provides the scrollview:
const FormAssignPlayer: React.FC<FormAssignPlayerProps> = ({
direction,
nextRoute,
}: FormAssignPlayerProps) => {
const playerAssignment = useAppSelector(selectUnconfirmedPlayers)[direction];
const [playerFilter, setPlayerFilter] = useState(
playerAssignment?.displayName ?? "",
);
const dispatch = useAppDispatch();
const roster = Object.entries(useAppSelector(selectPlayers)).map((e) => ({
playerId: e[0],
playerDisplayName: e[1].playerDisplayName,
}));
const idRef = useRef<TextInput>(null);
const router = useRouter();
useFocusEffect(() => {
log("useFocusEffect", "debug");
idRef.current!.focus();
});
const safeRegExp = safeMakeRegExp(playerFilter);
const filteredRoster = playerFilter
? roster.filter((player) => {
return player.playerDisplayName.match(safeRegExp);
})
: [];
log("afterFilter", "debug");
const handleSubmit = ({
playerId,
playerDisplayName,
}: Omit<Player, "clubId">) => {
setPlayerFilter(playerDisplayName);
dispatch(
setUnconfirmedPlayer({
dir: direction,
id: playerId,
displayName: playerDisplayName,
}),
);
router.replace(nextRoute);
};
return (
<View style={{ flexDirection: "row", flex: 1 }}>
<View style={{ flexDirection: "column", flex: 1 }}>
<TextInput
style={styles.inputField}
value={playerFilter}
onChangeText={setPlayerFilter}
selectTextOnFocus={true}
autoCapitalize={"none"}
placeholder={"⌨"}
placeholderTextColor="white"
multiline={true}
ref={idRef}
/>
</View>
<View
style={{
flexDirection: "column",
flex: 1,
}}
>
{playerFilter ? (
filteredRoster.length > 0 ? (
<ScrollView keyboardShouldPersistTaps={"handled"}>
{filteredRoster.map((player) => {
if (player.playerId) {
log("returningPlayerButton", "debug");
return (
<BorderedActionButton
key={player.playerId}
onPress={() => handleSubmit(player)}
inFlight={false}
>
<AppText style={[styles.listItem, { margin: 4 }]}>
{player.playerDisplayName}
</AppText>
</BorderedActionButton>
);
}
})}
</ScrollView>
) : (
<Centered>
<AppRealBig>No matches: "{playerFilter}"</AppRealBig>
</Centered>
)
) : null}
</View>
</View>
);
};
On Android, this works and keeps the tabs bar above the keyboard:
On iOS, the scrollView is still working, but the tabs bar does not appear above the keyboard:
How can I achieve the same behavior on iOS as I have on Android: that the expo-router (tabs) bottom-nav bar appears above the keyboard while it is open in portrait orientation, while preserving the functionality of the ScrollView (such that scrolling to the bottom of the scrollView brings the bottom element of the list above the keyboard and (tabs) bottom-nav bar, which the conditional KeyboardAvoidingView in TabWrapper was required to achieve for iOS)?
Upvotes: 0
Views: 115