How to use KeyboardAvoidingView with FlatList? - ios

I have a FlatList component with an Input inside each row. When I select the input I want it to scroll up above the keyboard.
My code:
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }} >
<FlatList
style={{ flex: 1, backgroundColor: '#fff' }}
data={ds}
renderItem={({ item }) => <ListItem data={item} />}
ListFooterComponent={this.renderButton}
/>
</KeyboardAvoidingView>
);
In this scenario, the FlatList is never loaded. When I delete flex:1 from both components, FlatList renders properly but selecting an Input does not make it scroll up

You can trying using react-native-keyboard-aware-scroll-view
https://github.com/APSL/react-native-keyboard-aware-scroll-view
It comes with KeyboardAware[ScrollView, ListView, SectionView, FlatList] which accepts the same props as their corresponding components from RN. I have used that and it worked for me.
render() {
return (
<KeyboardAwareFlatList
style={{flex: 1}}
data={this.state.data}
renderItem={({item}) => (
<View style={{flex: 1}}>
<Image
source={item.v}
style={{height:200, width: 200}}
/>
<TextInput
placeholder="enter text1"
/>
</View>
)}
/>
);
}

You could try using the library react-native-keyboard-spacer as an alternative to KeyboardAvoidingView.
Install:
npm install --save react-native-keyboard-spacer
Use it like this:
import KeyboardSpacer from 'react-native-keyboard-spacer'
...
<View style={{flex: 1}}>
<FlatList
style={{flex: 1}}
data={ds}
renderItem={({ item }) => <ListItem data={item} />}
/>
{/* The view that will expand to match the keyboard height */}
<KeyboardSpacer />
</View>

Try this:
<KeyboardAvoidingView behavior='position' keyboardVerticalOffset={xyz} >
You can remove the property 'keyboardVerticalOffset' or play with the value of xyz,
just find out the better value which fits in your case.

For anyone on a similar path as mine. I was not able to use KeyboardAvoidingView because it depends on ScrollView which conflicts with Flatlist. I couldn't used the header and footer option in Flatlist as I'm using it as a generated thing in a search selection box so it has to be contained.
For me there is a difference in how Android and iOS calculate absolute position. Android considers the bottom to be the top of the keyboard and iOS it is the bottom of the screen when the keyboard is showing.
It turns out to be not that difficult to just put a View around the content you want to remain above the keyboard and just dynamically set the height of it on iOS. This isn't even really necessary on Android as it follows the keyboard if the View is position: absolute and bottom: 0.
This heavily borrows from here: https://stackoverflow.com/a/60682069/438322
Thanks to Kevin Amiranoff
Here's a basic example using hooks.
function YourComponent(props){
const onKeyboardWillShow = e => {
setKeyboardHeight(e.endCoordinates.height);
};
const onKeyboardWillHide = () => {
setKeyboardHeight(0);
};
useEffect(() => {
// These listeners on ios are a little more snappy but not available on Android
// If you want to use this on Android use keyboardDidShow/Hide
if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', onKeyboardWillShow);
Keyboard.addListener('keyboardWillHide', onKeyboardWillHide);
}
return () => {
if (Platform.OS === 'ios') {
Keyboard.removeListener('keyboardWillShow', onKeyboardWillShow);
Keyboard.removeListener('keyboardWillHide', onKeyboardWillHide);
}
};
}, []);
const buttonHeight = 50;
return(
<View>
<Content bla={'bla'}/>
<View style={{
height: Platform.OS === 'ios'
? keyboardHeight + buttonHeight : buttonHeight,
position: 'absolute',
bottom: 0
}}>
{/* Keep this button above the keyboard */}
<Button style={{ height: buttonHeight }}/>
</View
</View>
)
}

this is my solution.
inverted={true} is the key
const dummy = [1,2,3,4,5,6,7,8,9,10]
<KeyboardAvoidingView >
<FlatList
data={dummy.reverse()}
inverted={true}
/>
</KeyboardAvoidingView>

Related

Keyboard covers one of the inputs in my React Native where view is centered using justifyContent

So I have this react native code where I centered multiple inputs inside view, when I click the lower located input my keyboard seems to overrun it so I cannot see what I'm typing on my iOS simulator (iPhone 14). The error seems to be on iOS only and Android is fine. note that I also have bottom Navigation bar.
https://ibb.co/sV0Xk9L
<SafeAreaView style={{ flex: 1}}>
{...}
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<View style={styles.profile_info}>
<KeyboardAvoidingView behavior={'position'} keyboardVerticalOffset={dimensions.height < 668 && phoneSelected ? 120 : 40}>
<ScrollView>
<View>
<Input />
<View/>
<View>
<Input />
<View/>
<View>
<Input />
<View/>
</ScrollView>
</KeyboardAvoidingView>
</View>
</TouchableWithoutFeedback>
</SafeAreaView>
{ // styling below }
function getProfileStyles(theme) {
return StyleSheet.create({
profile_info: {
flex: 1,
marginHorizontal: 20,
justifyContent: 'center',
},
});
}
I have tried using the solution found here https://stackoverflow.com/a/51169574/11288070 which uses #react-navigation/elements deps
import { useHeaderHeight } from '#react-navigation/elements'
something else that I do is changing from justifyContent to use topPadding: dimensions.height / 5 but it's not interactive.
Use:
KeyboardAwareScrollView from "react-native-keyboard-aware-scroll-view"
library instead of KeyboardAvoidingView

KeyboardAvoidingViev not working on iOS with react navigation header and material bottom tabs?

You find a minimal repo here and a minimal snack available here. They both show the issue nicely with very minimal code.
I found lots of questions online about how to use KeyboardAvoidingViev with react navigation e.g.
The popular proposed solution is
keyboardVerticalOffset={headerHeight + 64}
That does not work completely for me. The 64 seems arbitrary and somehow on iOS the view shrinks after closing the keyboard.
Here is what I know so far:
It is definitely react navigation (headers and/or Material Bottom Tabs Navigator) and KeyboardAvoidingViev working together. If you remove the Material Bottom Tabs Navigator, it works.
Solutions including SafeAreaView did make it worse
Solutions with behavior={"position"} did make it worse
Solutions with +64 are not working
I found out that the solution of David Scholz works nicely, if I remove the Material Bottom Tabs Navigator.
Everything works fine on android (Samsung Galaxy 7S and emulator). From what I know, I would recommend to avoid KeyboardAvoidingViev with react navigation on android. So in fact, KeyboardAvoidingViev is also not working fully for android, but you don't need to use it.
Any help is appreciated. Thank you!
I create a wrapping component like this and pass the offset from the screen so that it gets the current context.
import React from 'react'
import { ViewStyle } from 'react-native'
import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native'
type Props = {
children: any
keyboardVerticalOffset?: number
contentContainerStyle?: ViewStyle
}
export default function KeyboardWrapper({
children,
keyboardVerticalOffset,
...rest
}: Props): JSX.Element {
return (
<KeyboardAvoidingView
style={{ flex: 1 }}
keyboardVerticalOffset={keyboardVerticalOffset}
{...(Platform.OS === 'ios' ? { behavior: 'padding' } : {})}>
<ScrollView
bounces={false}
showsVerticalScrollIndicator={false}
{...rest}>
{children}
</ScrollView>
</KeyboardAvoidingView>
)
}
Usage in the screen
If you have any sticky elements like topbar or bottom bar you need to add the height so that the keyboard adds the offset correctly.
import { useHeaderHeight } from '#react-navigation/elements'
const height = useHeaderHeight()
<KeyboardWrapper
keyboardVerticalOffset={height}
contentContainerStyle={{ flex: 1 }}>
// ... your elements
</KeyboardWrapper>
This is a working demo https://snack.expo.dev/#raajnadar/fix-keyboardavoidingview-not-working
There is no need to add 64 to the headerHeight. Here is how I would solve this problem.
const flatListRef = createRef()
const [data, setData] = useState()
const headerHeight = useHeaderHeight();
<View style={{ flex: 1 }}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={headerHeight}
style={{ flex: 1 }}>
<FlatList
data={data}
ref={flatListRef}
onContentSizeChange={() =>
flatListRef.current.scrollToEnd({ animated: true })
}
onLayout={() => flatListRef.current.scrollToEnd({ animated: true })}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Text style={{ flex: 1, paddingVertical: 20 }}>{item.id}</Text>
)}
/>
<View style={{ flex: 1, marginBottom: 40 }}>
<TextInput
style={{
backgroundColor: '#2E2E2E',
width: '100%',
borderRadius: 18,
height: 36,
paddingLeft: 10,
paddingRight: 10,
color: '#FFFFFF',
}}
/>
</View>
</KeyboardAvoidingView>
</View>
Here is a quick and dirty snack which contains an header from a StackNavigator.

Is there a way for a TextInput to behave like a button i.e. be able to be selected (onPress)?

I am making a react native health app in which the user can select tags to describe certain symptoms they are facing. I am trying to let the user create their own tags, at the moment the user can enter tags but cannot select them. Is there a way to allow them to select text inputs?
I have already tried wrapping touchable opacity around it but when I press the text input the cursor just focuses on the word (wanting me to edit the word).
I have also tried editable = {false} this removes the ability for the user to enter a text input completely. Is there a way to allow the user to input a value once and then disable the text input (non-editable)?
Or If I used Button instead of TextInput is there a way for the user to enter the title of the button so it can act as a tag?
Here is how I have allowed users to create text inputs
addTextInput = (index) => {
let textInput = this.state.textInput;
textInput.push(
<TextInput
style={styles.textInput}
onChangeText={(text) => this.addValues(text, index)}
editable={true}
/>
);
this.setState({ textInput });
}
removeTextInput = () => {
let textInput = this.state.textInput;
let inputData = this.state.inputData;
textInput.pop();
inputData.pop();
this.setState({ textInput, inputData });
}
and this is what my current tags look like:
on the picture when the user presses the plus a new tag/TextInput is created, what I want is when the user presses it, it should be able to change color or the like.
here is the code for the plus button:
<View style={{
flexDirection: 'row',
flexGrow: '1',
flexWrap: 'wrap',
width: Responsive.width(300)
}}>
{this.state.textInput.map((value) => {
return value
})}
<TouchableWithoutFeedback onPress={() => {
this.addTextInput(this.state.textInput.length)
}}>
<Image
style={{ marginLeft: 8, width: 38, height: 38 }}
source={require('../../../assets/plusButton.png')}
/>
</TouchableWithoutFeedback>
{/* <Button title='Get Values' onPress={() => this.getValues()} /> */}
</View>
<View style={styles.row}>
<View style={{ margin: 10, top: Responsive.height(75) }}>
<Button onPress={() => this.removeTextInput()}>Remove</Button>
</View>
</View>
I had a similar problem and solved it with a TouchableOpacity over a TextInput. Using zIndex and absolute positioning you can expand the TouchableOpacity over your text input and transform it in a button, the TouchableOpacity must have a 100% opacity.
You can control with { condition && } your logic to make it behave like a button or a TextInput.
export default function App() {
const [m,setm]=React.useState(false);
return (
<View>
<TouchableOpacity style={{zIndex:10, position:'absolute', width: '100%', height: '100%', opacity:'100%'}} onPress={()=>{setm(!m)}} />
<TextInput style={{zIndex:1,backgroundColor:'#fbb',color:'#000'}} value={m}/>
</View>
);
}

How can I use this with FlatList not with ListView?

I took this code from react-native-gifted-chat
and I want to use FlatList
<ListView
enableEmptySections={true}
automaticallyAdjustContentInsets={false}
initialListSize={20}
pageSize={20}
{...this.props.listViewProps}
dataSource={this.state.dataSource}
renderRow={this.renderRow}
renderHeader={this.renderFooter}
renderFooter={this.renderLoadEarlier}
renderScrollComponent={this.renderScrollComponent}
/>
Here's an example for using FlatList to do what you're trying to do:
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
data={someArrayWithDataObjects} // your data source
renderItem={({ item }) => this.renderSearchResults(item)} // how you want each item rendered
keyExtractor={item => item.id} // unique identifier for performance reasons
/>
</View>
);
}

React-Native RefreshControl displaces Sticky Header iOS

Having an issue inside a ListView that uses sticky headers. When I scroll down while the refresh is still occurring, my sticky header is shifted down roughly equal to the height of the refresh control.
Any idea what's causing this?
Here is the code for my ListView and the refreshControl:
<View style={{backgroundColor: Colors.stickyHeaderGray, height: SCREEN_HEIGHT-20}}>
<ListView
ref='list'
style={this.container}
dataSource={this.state.ds}
initialListSize={12}
scrollRenderAheadDistance={0}
onEndReached={() => this.setState({ hideLoadingFooter: true })}
onEndReachedThreshold={10}
pageSize={10}
renderRow={(data) => <FeedItem {...data} navigator={this.props.navigator} />}
renderSeparator={this._renderSeparator}
renderSectionHeader={(sectionData, sectionID) =>
<View style={{backgroundColor: Colors.stickyHeaderGray, opacity: 0.96}}>
<TouchableHighlight
onPress={() => this.refs.list.scrollTo({x:0,y:0,animated:true})}
underlayColor={Colors.stickyHeaderGray}>
<View>
<Heading1 style={styles.headers}>{sectionID}
</Heading1>
</View>
</TouchableHighlight>
</View>
}
renderFooter={() =>
this.state.hideLoadingFooter ? null : <View style={styles.footer}><Heading1>Loading </Heading1><Spinner type={'ThreeBounce'} size={40} color={Colors.wabashRed} isVisible={true} /></View>
}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh}
tintColor={Colors.charcoal}
/>
}
/>
</View>

Resources