The KeyboardAvoidingView covers the TextInput UI while selecting emojis from the virtual keyboard.
Here is a sample code, KeyboardAvoidingView in this case works fine on entering the text from the virtual keyboard. however, on switching to the emoji picker in the virtual keyboard the automatically adjusted padding | height has no effect and the TextInput gets covered under the virtual keyboard.
import React from 'react';
import { View, KeyboardAvoidingView, TextInput, StyleSheet, Text,,TouchableWithoutFeedback, Keyboard } from 'react-native';
const KeyboardAvoidingComponent = () => {
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.inner}>
<Text style={styles.header}>Keyboard Avoiding View</Text>
...
<FlatList
...
<TextInput placeholder="Username" style={styles.textInput} />
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1
},
inner: {
padding: 24,
flex: 1,
justifyContent: "space-around"
},
header: {
fontSize: 36,
marginBottom: 50
},
textInput: {
height: 40,
borderColor: "#000000",
borderBottomWidth: 1,
marginBottom: 36
},
});
export default KeyboardAvoidingComponent;
There seems to be a bug in the RN library itself. In our project, I just patched the library with the changes you can also find in the PR I submitted to RN.
https://github.com/facebook/react-native/pull/34749
This ensures that the height is calculated correctly when switching keyboards.
Here is my current solution for this problem:
on Android I set android.softwareKeyboardLayoutMode: "pan" in Expo app.json (docs: https://docs.expo.dev/versions/latest/config/app/#softwarekeyboardlayoutmode) and I dont use any keyboard-avoid solution at all. In my experience Android handles text Input just fine out of the box with this config.
on ios I use KeyboardAvoidingView with behavior: "padding", which works fine for all keyboards (including Emoji). Please beware of behavior: "height" - it is bugged at the moment in RN 0.7.5 (https://github.com/facebook/react-native/issues/35764)
Here is my code:
React.createElement(Platform.OS === 'ios' ? KeyboardAvoidingView : View, {
style: container,
behavior: Platform.OS === 'ios' ? "padding" : null,
},
... // children
)
Please also take a look at a great article explaining modern ways of keyboard avoiding for IOS, Android in different cases https://www.netguru.com/blog/avoid-keyboard-react-native
Related
I am trying to create a view post ui for a social media feature of our app. We have a post (the flatlist header component), the comments (the flatlist), and a fixed bottom text input that leverages zIndex to be placed over the flatlist to post comments. The problem is I cannot for the life of me figure out how to properly use a keyboard avoiding view to somehow push this fixed input up when the keyboard is shown. Is there any way to do this or maybe a simpler approach to this ui that I'm missing? I have tried using the keyboard height and adding it to the bottom positioning of the input but its slow and definitely isn't viable.
Code:
<View style={{flex: 1}}>
<View
style={{
...styles.container,
marginTop: 0,
zIndex: 1,
}}>
<FlatList
style={{minHeight: '100%'}}
data={new Array(15)}
renderItem={({item, index}) => <CommentRow index={index} />}
keyExtractor={(item, index) => `${index}`}
ListHeaderComponent={renderHeader}
contentContainerStyle={{paddingBottom: 70}}
/>
</View>
<View
style={{
position: 'absolute',
bottom: 0,
backgroundColor: 'white',
width: '100%',
zIndex: 2,
padding: 15,
borderTopWidth: 1,
borderColor: '#f2f2f2',
}}>
<View style={styles.inputContainer}>
<TextInput style={styles.input} placeholder="Add a comment.." />
<View style={styles.inputTextContainer}>
<Text style={styles.inputText}>Post</Text>
</View>
</View>
</View>
</View>
);
I'm not a fan of keyboard avoiding view because of some bad experiences I had. However, I'll give some tips on how to achieve the same effect with React Native core Animated Library.
Since your view is elevating (Style it as an absolute view) and when the keyboard appears, you can get the height of the keyboard with Keyboard addListener's callback.
componentDidMount(){
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
}
_keyboardDidShow(e) {
const keyboardHeight = e.endCoordinates.height,
}
Once you get the keyboard height, you can animate the absolute view using translateY to the position that aligns with the keyboard without any gaps. This will provide an nice animation similar to that you tried to accomplish with keyboard avoiding view. I've done that in few apps as well and it works great.
For anyone looking to do anything like this - what I ended up doing is using an useEffect hook to monitor when "keyboardWillShow" and then created a function to animate the view above the keyboard once its about to show. A lot of the answers I had seen used "keyboardDidShow" but it makes the UI slow and really ugly. Here's the code:
useEffect(() => {
Keyboard.addListener('keyboardWillShow', _keyboardWillShow);
Keyboard.addListener('keyboardWillHide', _keyboardWillHide);
// cleanup function
return () => {
Keyboard.removeListener('keyboardWillShow', _keyboardWillShow);
Keyboard.removeListener('keyboardWillHide', _keyboardWillHide);
};
}, []);
const _keyboardWillShow = (e) => {
const keyboardHeight = e.startCoordinates.height;
Animated.timing(topValue, {
toValue: -keyboardHeight,
duration: 50,
useNativeDriver: true,
}).start();
};
const _keyboardWillHide = (e) => {
Animated.timing(topValue, {
toValue: 0,
duration: 0,
useNativeDriver: true,
}).start();
};
Where topValue is a stateful Animated value. All you need to do after this is add the topValue variable to your animated view using transform and you're done!
I've been building react-native for android at beginning and never been into IOS as I don't have project that use IOS at that moment and I don't own IOS or MacOS. And I'm clueless of what is difference between IOS and Android in general. I'm currently need to develop my app to be able to use by both Android and IOS.
What are the common things that need to be handle? To be exact, check for Platform. From maestral-solutions, it shows on stylesheet that the header height and margin top:-
const styles = StyleSheet.create({
header: {
height: Platform.OS === 'android' ? 76 : 100,
marginTop: Platform.OS === 'ios' ? 0 : 24,
...Platform.select({
ios: { backgroundColor: '#f00', paddingTop: 24},
android: { backgroundColor: '#00f'}
}),
alignItems: 'center',
justifyContent: 'center'
},
text: {
color: '#fff',
fontSize: 24
}
});
Is there any other common things to handle for IOS platform? Like status bar or tab navigation or icon?
You can use SafeAreaView instead of View for wrap.
for example:
render() {
return (
<SafeAreaView>
<View style={{backgroundColor: 'red'}} />
</SafeAreaView>
);
}
If you wrap with View then the header will cutted when you are using iPhoneX since iPhoneX has different UI with others.
And also there are some other things different in style.
In iOS you should add overflow: 'hidden' for borderRadius. Means, you can only use borderRadius in Android but you can see the circular border after add overflow: 'hidden'. And I think the backgroundColor will works in Text component in Android but not in iOS.
Then you should care about Alert.alert in iOS. in Android you can normally use Alert and setState in the same time. But if you use Alert and setState in the same time then alert disappear right after show. For break down this you can use like this.
setTimeout(() => {
Alert.alert('info', 'Testing');
}, 100);
this.setState({spinner: false});
You can check this will works well in iOS too.
I am working on a react native app which involves a video player (react-native-video), and some simple controls I set up myself. on iOS this works fine, but on Android the TouchableOpacity elements, which I use for controls and navigation, don't seem to detect touches. (Navigation is handles by react-native-fluid-transitions in my app). When I turn on the inspector, a screen-covering View seems to be on top of my controls. However, this is not the case on iOS and I have also not configured such a view.
I installed Atom to use it's inspector feature to see the actual order of my views. It looks as follows:
VideoView is the name of my component, Video is the actual video player and the TouchableOpacity I highlighted is the button I'm trying to get to work. In this view hierarchy, no views seem to be on top of anything. I have also compared this breakdown to other components where my buttons actually work and it looks the same.
My code looks as follows:
return (
<View style={internalStyles.container}>
<Video style={internalStyles.videoContainer}
ref={(ref) => {
this.props.player = ref
}}
source={{uri: url}}
controls={false}
onEnd={() => this.videoEnded()}
paused={this.state.paused}
muted={false}
repeat={false}
resizeMode={"contain"}
volume={1.0}
rate={1.0}
ignoreSilentSwitch={"obey"}
/>
{this.renderControls()}
{Renderer.getInstance().renderNavigationButton()}
</View>
);
where renderControls is a function that renders the pause button, and Renderer is a singleton component containing render function for items I use in more components of my app. This all works fine on iOS, but not on Android. react-native-video seems to be incompatible with react-native-fluid-transitions as everything works when I remove one of either.
Does anyone know what might cause this behavior? Any help would be highly appreciated.
Try removing the activeOpacity prop from TouchableOpacity component.
Or you can use platform specific code to set values for activeOpacity prop
import { Platform, TouchableOpacity } from 'react-native'
<TouchableOpacity
activeOpacity={Platform.OS==='android' ? 0 : 0.2}
>
<Text>submit</Text>
</TouchableOpacity>
Wrap the component in a view and disable pointer events.
<View pointerEvents="none">
<Video source={{ uri: source }} />
</View>
import {TouchableOpacity} from 'react-native';
<TouchableOpacity>some text</TouchableOpacity>
For me it was solved by putting zIndex:1000
<TouchableOpacity
onPress={this.handlePause}
style={{
position: "absolute",
alignItems: "center",
justifyContent: "center",
alignSelf: "center",
elevation: 2,
backgroundColor: "#FFF",
width: 60,
height: 60,
borderRadius: 30,
flex: 1,
zIndex: 1000,
}}
>
I am trying to build a ReactNative Application with an animated button. The problem is that this animation does not work correctly the first time after the App is started. There is some white flickering. But after the animation ran wrong the first time everything works as expected:
I have already tried to preload the image in several ways, but without any success.
This is my minimal working example, note that if there are several different images the flickering occurs if a new image is loaded (e.g. I have two blue buttons and after I tapped the first one, the second one will work fine, but if I then tap an orange button it once again flickers for the first time, at least if I have not tapped another orange button after app start.):
import React, { Component } from 'react';
import {StyleSheet, Text, TouchableWithoutFeedback, View, Image, ScrollView,
Button, BackHandler} from 'react-native';
export default class Touchables extends Component {
constructor(props) {
super(props);
this.state = {alarm1: (<Image source={require("./assets/alarmoff.png")}
style={styles.imageButton}/>),
}
}
componentWillMount(){
//trying to preload all Images, but it does not help.
(<Image source={require("./assets/alarmon.png")} style=
{styles.imageButton}/>)
}
render() {
return (
<ScrollView style={styles.contentContainer}>
<View style={{flex: 3, flexDirection: 'row'}}>
<View style={styles.container}>
<TouchableWithoutFeedback onPressIn={() => this.setState({alarm1:
<Image source={require("./assets/alarmon.png")} style={styles.imageButton}/>})} onPressOut={() => this.setState({alarm1: <Image source={require("./assets/alarmoff.png")} style={styles.imageButton}/>})}>
<View style={styles.button}>
{this.state.alarm1}
</View>
</TouchableWithoutFeedback>
<Text style={styles.text}>This button flickers on first click. Restart App completly to see the issue. Reloading is not enough.</Text>
</View>
</View>
<View>
<Button
onPress={() => BackHandler.exitApp()}
title="Exit App"
color="#841584"
accessibilityLabel="Android only I guess."
/>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 2,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 30
},
button: {
backgroundColor: '#fff',
borderRadius: 20,
padding: 10,
marginBottom: 20,
shadowColor: '#303838',
shadowOffset: { width: 0, height: 5 },
shadowRadius: 10,
shadowOpacity: 0
},
contentContainer: {
paddingVertical: 20,
flex: 1,
backgroundColor: '#fff',
},
text:{
color: '#000',
marginBottom: 30
},
imageButton: {
flex: 1,
width: 240,
height: 200,
marginBottom: -15,
marginTop: 10,
resizeMode: 'cover'
}
});
So my question is how can I stop the image from flickering after app start?
The full version of the little demo app I have build to show my problem is available on my Github Repository
There may be a performance issue while loading different resolution images. You can use https://github.com/DylanVann/react-native-fast-image module to load images.
you can add and link it as below
# Install
yarn add react-native-fast-image
# Automatic linking. (other linking methods listed below)
react-native link react-native-fast-image
after that u can import it and use it using like below example
import FastImage from 'react-native-fast-image'
const YourImage = () =>
<FastImage
style={styles.image}
source={{
uri: 'https://unsplash.it/400/400?image=1',
headers:{ Authorization: 'someAuthToken' },
priority: FastImage.priority.normal,
}}
resizeMode={FastImage.resizeMode.contain}
/>
I copied this example from that repo. you can find documentation also there. Try it. It will increase image loading performance. Then most probably flickering issue will be resolved.
For me, it was causing flickering issues when I put the Image component in a FlatList ListHeaderComponent component. So,
Code causing flickering:
ListHeaderComponent={HeadComponent}
The HeadComponent was basically inside render and had the code const HeadComponent = () => { return (<Image...
Code that fixed flickering:
ListHeaderComponent={this.renderHeader}
The renderHeader is a function that returned the same thing as HeadComponent using code renderHeader () { return (<Image...
Hope this helps someone.
It was causing flickering issues when I put the Image component in a FlatList ListHeaderComponent component
In order to solve the issue I added useCallBack hook
const ListComponent = useCallBack(() => {
// your code
}, [])
ListHeaderComponent={ListComponent}
For me it solved the flickering issue
Well I have a workaround (sort of..).
In my componentDidMount() I do now set the button to its pressed state, wait for some time until the image is displayed and scaled, and then I set the state to off again, like so:
componentDidMount(){
this.setState({alarm1: <Image source={require("./assets/alarmon.png")} style={styles.imageButton}/>})
setTimeout(()=>{this.setState({alarm1: <Image source={require("./assets/alarmoff.png")} style={styles.imageButton}/>})}, 1000);
}
I tried to lower the timeout to less than a second, but then on my old (and slow) phone the flickering started again on first press after app load.
This obviously leads to the button state beeing changed after the app loaded, but if all buttons flicker once after app start, that is better than every button flickering on first press in my opinion.
I would however be glad if anybody could tell me the real way, how to resolve this.
For anyone who still have this problem : this is another way to fix
<Image
source={{ uri: your_path }}
defaultSource={{ uri: your_path }}
resizeMode="cover"
style={{width: 100,height: 100}} />
A stupid way:
<ImageBackground
defaultSource={require('./ui/pay0.png')}
source={require('./ui/pay0.png')}
style={{flex: 1, position: 'relative', zIndex: 9999, elevation: 5}}
/>
{/* Cache the remaining pictures to prevent flickering */}
<ImageBackground
source={require('./ui/pay1.png')}
style={{position: 'absolute', width: 1, height: 1}}
/>
If you are using expo, you can use Asset.loadAsync. See: https://docs.expo.io/versions/latest/sdk/asset.
In App.js, I like to wait for all static assets to be loaded before showing any screen.
I have encountered a problem porting my app from iOS to android. I have built a minimal bug case with which it can be reproduced.
My app is very simple and consists of one component:
const Main = () => (
<View>
<View style= {styles.green}>
<View style={styles.blue}/>
</View>
</View>
)
export default Main;
const styles = StyleSheet.create({
green: {
height:200,
marginTop: 200,
backgroundColor: 'green',
},
blue:{
position:'absolute',
height: 100,
width: 100,
top: -50,
backgroundColor: 'blue',
},
})
But strangely this component renders differently on iOS and Android
iOS
Android
I would like it to render like on iOS on both devices. You can check code on this repo https://github.com/42void/ReactNativeBug to easily reproduce it.
Thank you!
In Android overflow property defaults to 'hidden' and cannot be changed.
From 0.23 known issues, apparently isn't fixed yet.