Reputation: 2322
The goal: create a text input field that responsively expands as input lines expand, up to a certain point, like most messaging apps.
The problem: as input expands past 3 or 4 lines, instead of the top of the input container expanding upwards, the input container expands based on content size, as it should, but the container is repositioned behind the keyboard/keyboard word suggestions, which re-positions the button on the left (part of the input container) as well as the <TextInput>
itself.
the picture below shows how I want the padding to be on the bottom. the "lower container" embodies the button and the input field. the off-grey color is the top of the keyboard suggestions (iOS)
The image below shows the undesired behavior. you can see the bottom border of the input, as well as the button start to disappear behind the suggestion box/keyboard. If the input kept expanding, the bottom border and button would progressively become less visible.
Here is the whole screen component:
render () {
return (
<KeyboardAvoidingView
style={{ flex: 1, backgroundColor: Colors.PRIMARY_OFF_WHITE,
}}
behavior={Platform.OS === 'ios' ? 'position' : null}
keyboardVerticalOffset={Platform.OS === 'ios' ?hp("11%") : 0}
>
/* Upper Container */
<View style={{ justifyContent: 'flex-start', height: height*0.8, paddingBottom: 10 }}>
<FlatList
data={this.props.messages}
ref={'list'}
onContentSizeChange={() => this.refs.list.scrollToEnd()}
keyExtractor={(item) => {
return (
item.toString() +
new Date().getTime().toString() +
Math.floor(Math.random() * Math.floor(new Date().getTime())).toString()
);
}}
renderItem={({ item, index }) => {
return (
<Components.ChatMessage
isSender={item.sender == this.props.uid ? true : false}
message={item.content.data}
read={item.read}
time={item.time}
onPress={() =>
this.props.readMsg(
{
id: this.props.uid,
role: this.props.role,
convoId: this.state.ownerConvoId
},
item.docId,
this.props.navigation.state.params.uid
)}
/>
);
}}
/>
</View>
/* Lower Container */
<View
style={{
height: this.state.height+20,
flexDirection: 'row',
alignItems: 'center',
justifyContent:"space-around",
paddingVertical: 10
}}
>
<Components.OptionFan
options={[
{
icon: require('../assets/img/white-voice.png'),
onPress: () => {
this.props.navigation.navigate('Schedule', {
receiver: this.conversants.receiver,
sender: this.conversants.sender,
type: 'Voice'
});
},
key: 1
},
{
icon: require('../assets/img/white-video.png'),
onPress: () => {
this.props.navigation.navigate('Schedule', {
receiver: this.conversants.receiver,
sender: this.conversants.sender,
type: 'Video'
});
},
key: 2
}
]}
/>
<TextInput
multiline={true}
textAlignVertical={'center'}
onChangeText={(text) => this.setState({ text })}
onFocus={() => this.refs.list.scrollToEnd()}
onContentSizeChange={(event) =>
this.setState({
height:
event.nativeEvent.contentSize.height <= 40
? 40
: event.nativeEvent.contentSize.height
})}
style={{
height: Math.max(40, this.state.height),
width: wp('80%'),
borderWidth: 1,
alignSelf: this.state.height > 40 ? "flex-end":null,
borderColor: Colors.PRIMARY_GREEN,
borderRadius: 18,
paddingLeft: 15,
paddingTop: 10,
paddingRight: 15
}}
ref={'input'}
blurOnSubmit={true}
returnKeyType={'send'}
placeholder={'Write a message...'}
onSubmitEditing={() => {
if (this.props.role == 'Influencer') {
this.send();
this.refs.input.clear();
} else {
this.setState({ visible: true });
}
}}
/>
</View>
</KeyboardAvoidingView>
);
}
How can I go about implementing an outgrow that stays in view with the desired padding on the bottom?
EDIT: to be clear, this question is not about how to make an auto-grow input field. the code above already accomplishes that. its about the container the auto-grow input field is in, and how the parent container, input field, and button grow behind the the keyboard/keyboard suggestions. the Goal is to have the text input always display directly above the keyboard, with the bottom of the input/button/container consistently the same space between the keyboard, while the height expands upward only.
the question posted here is similar: In React Native, How to have multiple multiline textinputs that don't hide behind the keyboard in one scrollable form?
Upvotes: 1
Views: 4518
Reputation: 2322
As an additional note, when factoring in dynamic heights of portions of your screen, always remember to account for status bars (both iOS and android obviously) as well as the pesky Soft Android Navigation Bar at the bottom of the screen, as its not factored in when using the Dimension api for 'window'. My mistake to ignore this.
to get Status bar and nav bar:
import {NativeModules, Platform} from 'react-native';
const { StatusBarManager } = NativeModules; // Note: StatusBar API is not good used here because of platform inconsistencies.
// Note: 'window' does not contain Android/iOS status bar or Android soft Navigation Bar
const WINDOW_HEIGHT = Dimensions.get('window').height;
// 'screen' gets full hardware dimension
const DEVICE_HEIGHT = Dimensions.get('screen').height;
const STATUS_BAR_HEIGHT = Platform.OS === 'ios' ? 20 : StatusBarManager.HEIGHT;
const ANDROID_NAV_HEIGHT = Platform.OS === 'ios' ? 0 : DEVICE_HEIGHT - (WINDOW_HEIGHT + STATUSBAR_HEIGHT);
Upvotes: 0
Reputation: 6652
You can set the parent container to be '100%' height and resize the upper content so that the input container always grows upwards.
A simple implementation of this is as follows. textInputHeight
is taken from the state and it is set when TextInput
resizes.
return (
<KeyboardAvoidingView
style={{height: '100%'}}
behavior={Platform.OS === 'ios' ? 'position' : undefined}
keyboardVerticalOffset={100}>
<ScrollView
style={{
backgroundColor: '#777',
height: height - (80 + textInputHeight),
}}>
<View style={{height: 1000}} />
</ScrollView>
<TextInput
multiline={true}
onLayout={(event) => {
if (textInputHeight === 0) {
setTextInputHeight(event.nativeEvent.layout.height);
}
}}
onContentSizeChange={(event) => {
setTextInputHeight(event.nativeEvent.contentSize.height);
}}
/>
</KeyboardAvoidingView>
);
Upvotes: 2