I have an issue with scroll view inside Swipe model using react-native-modals. The problem is in Android the scrolling is smooth and working perfectly. However, with iOS scrolling is with short amount only it scrolls for tiny amount then it stops meaning i have to repeat scrolling for 10 times over the screen to view the bottom with small contents only. This is the code for the model.
<Modal
propagateSwipe={true}
visible={this.props.visible}
useNativeDriver={true}
modalAnimation={
new SlideAnimation({
initialValue: 0, // optional
slideFrom: 'bottom', // optional
useNativeDriver: false, // optional
})
}
style={stylesSwipeCard.modal}
rounded={true}
modalStyle={[
stylesSwipeCard.customModal,
this.props.customModalStyle,
{maxHeight: height * this.props.height},
]}
onTouchOutside={() => this.props.closeModal(false)}
swipeDirection={['down']} // can be string or an array
swipeThreshold={100} // default 100
onSwipeOut={(event) => {
this.props.closeModal(false);
}}
containerStyle={stylesSwipeCard.modalContainer}
footer={this.props.hasFooter && this.props.footerContent}>
<ModalContent style={stylesSwipeCard.modalContent}>
<View
style={[stylesSwipeCard.container, this.props.modalContainerStyle]}>
<Image source={line_swipe} style={{alignSelf: 'center'}} />
<ScrollView>
<TouchableHighlight>
<TouchableWithoutFeedback>
<View>{this.props.children}</View>
</TouchableWithoutFeedback>
</TouchableHighlight>
</ScrollView>
</View>
</ModalContent>
I would appreciate any help or suggestions 😄. Thanks
I am using the Menu component from react-native-paper for options menu on modal-header.
Below is the screenshot of the modal:
The parent tag holding the Menu has sibling elements (stuff below the header).
It seems that due to this heirchy, the menu is being rendered under other elements.
I tried override this overlaping of elements by assigning possition:"absolute", zIndex: 100.
zIndex is haveing no effect on the way it is being overlaped. I tried varying the zIndex from 1 to 1500, but its had n effect either.
Following is the code for Menu Component wrapper (ModalOptions):
const ModalOptions = () => {
const [visible, setVisible] = React.useState(false);
const openMenu = () => setVisible(true);
const closeMenu = () => setVisible(false);
return (
<Provider>
<View>
<Menu
style={{ backgroundColor: "#222", borderWidth: 2, top:150, left:-100 , position: 'absolute', zIndex:100 }}
visible={visible}
onDismiss={closeMenu}
anchor={
<TouchableOpacity onPress={openMenu}>
<ThreeDotIcon size={35} color={colors.darkGrey} />
</TouchableOpacity>
}>
...
</Menu>
</View>
</Provider>
);
};
I guess I'm not using zIndex properly...
If so, how should I be using it instead?
If not, is there any other way to get this done?
or maybe I need to re format the code in such way that the heirchal level of Menu is increased
but I would really not prefer going this way.
You can use a View to wrap outsize the provider with style={{zIndex: 100}}, like below
<View style={{zIndex: 100}}>
<Provider>
<View>
<Menu
style={{ backgroundColor: "#222", borderWidth: 2, top:150, left:-100 , position: 'absolute', zIndex:100 }}
visible={visible}
onDismiss={closeMenu}
anchor={
<TouchableOpacity onPress={openMenu}>
<ThreeDotIcon size={35} color={colors.darkGrey} />
</TouchableOpacity>
}>
...
</Menu>
</View>
</Provider>
</View>
Should we use the Loader component (Any custom Loader) in every Component and use dedicated state reducer variables to toggle it with a relative API call or should we have a Loader in the root of the application and toggle it on any API instance?
If we use a root Loader component, and it has properties
{position: 'absolute', top:0, bottom:0, right:0, left:0}
(Full-screen loader). Although it would get rid of many lines of code to toggle every loader component separately, but wouldn't it stop the user from any other page if one API endpoint crashes or takes too long to load.
What would the best practice be?
I wrap all of my screen with custom component, so I have a component that is wrapped all screen and I show loading on this component:
ScreenContainer component:
function ScreenContainer({
barStyle = "dark-content",
statusBarColor = Colors.whiteFFF,
children,
containerStyle,
loading = false,
translucent = false,
}: ScreenContainerProps) {
useFocusEffect(
React.useCallback(() => {
StatusBar.setBarStyle(barStyle);
StatusBar.setBackgroundColor(statusBarColor);
StatusBar.setTranslucent(translucent);
}, []),
);
return (
<View
style={[
{
flex: 1,
},
containerStyle,
]}
>
{loading ? <LoadingOverlay show={loading} /> : null}
{children}
</View>
);
}
LoadingOverlay Component:
function LoadingOverlay({ show = false }: LoadingOverlayProps) {
return (
<Modal
transparent
visible={show}
animated
animationType="fade"
presentationStyle="overFullScreen"
>
<StatusBar backgroundColor="rgba(0,0,0,0.3)" barStyle="light-content" />
<View
style={{
backgroundColor: "rgb(33, 33, 33)",
opacity: 0.4,
alignItems: "center",
justifyContent: "center",
flex: 1,
}}
>
<TLoader />
</View>
</Modal>
);
}
I am trying to make a simple application/game to learn and practice React Native. Currently, I am trying to create a sticky TouchableNativeFeedback that moves with the screen as I am using the ScrollView.
The idea is that left half of the screen would move the character to the left and the right half of the screen would move it to the right. I want these controls to be fixed in the display and not move.
This is how it starts
This is after moving the scrollView a bit
I've initially tried to change value of style.left as I scrolled but that doesn't seem to be a good/stable solution.
Here is the current code:
render() {
return (
<ScrollView
//onScroll={this._onScroll}
ref={(scrollView) => { this.scrollView = scrollView; }}
style={styles.container}
horizontal= {true}
snapToAlignment={"center"}
>
<View style={styles.wrapperView}>
<ImageBackground
style={styles.container}
source={require('../images/second.png')}
>
<TouchableNativeFeedback onPressIn={this._onPressButton} onPressOut={this._onPressButton}>
<View style={
{
width: width/2,
height: '100%',
position: 'absolute',
left: this.state.touchableLeft,
backgroundColor: 'red',
}
}>
</View>
</TouchableNativeFeedback>
... (code about the character)
</ImageBackground>
</View>
</ScrollView>
);
}
and the styles code
const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
},
wrapperView:{
width: width*3 + 300,
},
});
and just to have it as a reference, this is what I originally tried:
_onScroll = (event) => {
this.setState( {
touchableLeft: this.state.touchableLeft + event.nativeEvent.contentOffset.x
} )
}
I've looked at the following questions and articles but I couldn't really get to a solution that would help me. Usually people use flex to make their headers sticky above a ScrollView and that is incredibly handy but in this situation I am unsure about how to continue. Articles/Questions:
How to Get ListView Section Header to Stick
http://docs.nativebase.io/docs/examples/StickyHeaderExample.html
Sticky Component inside scrollview
What solved my problem was to take the TouchableNativeFeedback outside of the class. The class in the question was called Background and it was rendered in the class called App.
<View style={styles.container}>
<Background />
<TouchableNativeFeedback onPressIn={this._onPressButton} onPressOut={this._onPressButton}>
<View style={
{
width: '50%',
height: '100%',
position: 'absolute',
left:0,
//left: this.state.touchableLeft,
//backgroundColor: 'red',
}
}>
</View>
</TouchableNativeFeedback>
</View>
As you can see once I moved it to here, it was positioned right where I wanted, even if I move the screeen. What would be good practice, is to take it to another component class and then just call an instance of it.
Thanks to John Ruddell for the help while coming up with the solution
I'm using SafeAreaView from React Native 0.50.1 and it's working pretty good except for the one part. I assigned the orange background color to the SafrAreaView but can't figure out to change the bottom unsafe area background to black.
Here is the code and I included expected the result and actual result.
What is the best way to make the bottom part of the screen black instead of orange?
import {
...
SafeAreaView
} from 'react-native';
class Main extends React.Component {
render() {
return (
<SafeAreaView style={styles.safeArea}>
<App />
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
...,
safeArea: {
flex: 1,
backgroundColor: '#FF5236'
}
})
I want to have orange top and black bottom.
But below is what I get now.
I was able to solve this using a version of Yoshiki's and Zach Schneider's answers. Notice how you set the top SafeAreaView's flex:0 so it doesn't expand.
render() {
return (
<Fragment>
<SafeAreaView style={{ flex:0, backgroundColor: 'red' }} />
<SafeAreaView style={{ flex:1, backgroundColor: 'gray' }}>
<View style={{ flex: 1, backgroundColor: 'white' }} />
</SafeAreaView>
</Fragment>
);
}
In 2022, I am able to solve this by using edges prop of SafeAreaView!
import { SafeAreaView } from 'react-native-safe-area-context';
<>
<SafeAreaView
edges={["top"]}
style={{ flex: 0, backgroundColor: "#0a2352" }}
/>
<SafeAreaView
edges={["left", "right", "bottom"]}
style={{
flex: 1,
backgroundColor: "#fff",
position: "relative",
}}
>
...
</SafeAreaView>
</>
I was able to solve this by using some absolute position hacking. See the following tweak. Not future proof by any means, but it solves the problem I had.
import {
...
SafeAreaView,
View
} from 'react-native';
class Main extends React.Component {
render() {
return (
<SafeAreaView style={styles.safeArea}>
<App />
<View style={styles.fixBackground} />
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
...,
safeArea: {
flex: 1,
backgroundColor: '#FF5236'
},
fixBackground: {
backgroundColor: 'orange',
position: 'absolute',
bottom: 0,
right: 0,
left: 0,
height: 100,
zIndex: -1000,
}
})
I ran into the same problem and was able to solve with the following:
const styles = StyleSheet.create({
outerWrapper: {
backgroundColor: 'orange',
},
innerWrapper: {
backgroundColor: 'black',
},
});
// snip
const MyComponent = ({ content }) => (
<View style={styles.outerWrapper}>
<SafeAreaView />
<SafeAreaView style={styles.innerWrapper}>
{content}
</SafeAreaView>
</View>
);
The outerWrapper applies the orange background color at the top. The first <SafeAreaView /> pushes the second one down so that it starts at the beginning of the "safe area" (below the status bar). Then the second SafeAreaView takes up the rest of the screen (including the bottom "unsafe" area) and gives it the black background color.
SOLUTION :
I ran into this problem but I don't understand the struggle you guys had to solve it.
The app is in a container that takes all the screen. SafeAreaView will wrap the app into a new smaller container. So add the style into the main container so you can have the background you want. As easy as that :
<View style={{flex:1, backgroundColor: "black"}}>
<SafeAreaView style={{flex:1, backgroundColor: "white"}}>
<App />
</SafeAreaView>
</View>
That's all, hope it helps some of you ;)
You can return multiple SafeAreaViews from your render method using Fragment, each of which independently specify their backgroundColor:
render = () => (
<Fragment>
<SafeAreaView style={{ flex: 0.5, backgroundColor: "red" }} />
<SafeAreaView style={{ flex: 0.5, backgroundColor: "blue" }} />
</Fragment>
);
Result on an iPhone X:
If you need different color of SafeAreaView's top or bottom bar, just cover the bar with View component (positioned absolute) and get its height from useSafeAreaInsets hook.
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
<SafeAreaView style={{ flex: 1 }}>
<View style={{
position: 'absolute'
height: useSafeAreaInsets().top,
backgroundColor:'red'
}} />
</SafeAreaView>
<Fragment> not work correctly on any Android devices, so to solve the issue, i try this solution and it work on both android and ios:
<SafeAreaView style={{ flex:1, backgroundColor: 'red' }}>
<View style={{ flex: 1, backgroundColor: 'green' }} >
<Text>Hello</Text>
</View>
</SafeAreaView>
I just ran into the same problem, we have a navigation bar at the top and a tab bar at the bottom with two different background colors.
My solution was to wrap the tab bar component in a SafeAreaView with the correct background color, along with wrapping the navigation bar component with its own SafeAreaView with its background color.
return (
<SafeAreaView style={{ backgroundColor: 'white' }}>
<Animated.View style={heightAnim}>
{...navBarComponentStuff}
</Animated.View>
</SafeAreaView>)
For the Navigation Bar
And this for the Tab Bar:
<SafeAreaView style={this.props.tabBarStyle}> //Some dark grey color is passed here
<Animated.View style={heightAnim}>
{tabBarItems.map(render tabs.....}
</Animated.View>
</SafeAreaView>}
So, that being said, you could wrap your navigation component in a SafeAreaView with the orange color set in style, and wrap your main content in another SafeAreaView with black as the backgroundColor style, or whatever color you have chosen.
One thing you should know, if you add two SafeAreaView's in the same component like:
return (
<View style={styles.container}>
<SafeAreaView style={{ backgroundColor: 'white' }}>
<NavigationBarComponent />
</SafeAreaView>
<SafeAreaView style={this.props.tabBarStyle}>
<Animated.View style={heightAnim}>
<Animated.View style={[styles.tabBar, this.props.tabBarStyle]}>
{map and render tabs}
</Animated.View>
</Animated.View>
</SafeAreaView>
</View>
);
It will combine the two SafeAreaView's, or at least thats what it looked like to me, maybe somebody with more experience with this can explain what happens in this situation.
This pushed my tab bar to the top of the screen and set the background color to white when I did this.
But moving the top SafeAreaVeiw into the NavigationBarComponent gave me the desired effect.
For those who may be interested, and maybe this is the reason why it acted funky with two of them in the same view, the AnimatedView's are because we sometimes hide the nav and tab bars.
I was able to achieve this by the code below,
<View style={{
flex: 1
}}>
<SafeAreaView style={{
flex: 0,
backgroundColor: 'red'
}}>
</SafeAreaView>
<View style={{
flex: 1,
top: 0,
backgroundColor: 'white'
}}>
</View></View>
Thanks for #Daniel M, inspired by his answer.