React Native TouchableWithoutFeedback on view with position absolute - react-native

I have a modal that I want show when the user clicks a i button.
The modal envelops the entire screen, by setting the position to 'absolute'. This works fine, but when the user clicks somewhere on the screen, it should disappear again.
My TouchAbleWithoutFeedback however, does not register a press. It appears to be having something to do with the position being absolute but I can't put my finger on it.
Also, the clickable elements underneath the modal are still interactable, even though the modal is displayed on top of those elements.
Here is my code:
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
render()
{
return (<View>
<View style={styles.container}>
<OtherElements/>
</View>
<MainScreenInfoModal showModal={this.state.showInfoModal} />
</View>);
}
const MainScreenInfoModal = ({showModal}) =>
{
if (showModal)
{
return <TouchableWithoutFeedback style={{zIndex: 10}} width={window.width} height={window.height} onPress={() => {console.log('press!')}}>
<View style={styles.infoModal} >
<SvgXml width={300} height={300} xml={mainscreenInfomodal} style={{
alignSelf: 'center',
marginTop: window.height * .45
}}/>
</View>
</TouchableWithoutFeedback>
}
return null;
}
const styles = StyleSheet.create({
container:
{
fontSize: 26,
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
padding: 13,
fontWeight: "bold",
},
{...} ,
infoModal:
{
position: 'absolute',
bottom: 0,
width: window.width,
height: window.height,
backgroundColor: 'rgba(255,255,255,0.7)',
zIndex: 10
}});

try react-native-modalize library, it saved me alot of time and nerves developing modals

Related

How to achieve drop-shadow with no blur in React Native

I'm new using React Native and I'm trying to map the following component (made in web) but for React Native with no success:
Elevation and shadow properties does not do the trick because they add some blur to the resulting shadow. Which would be the proper way to handle this?
Regards
Use react-native-cardview
import React, { Component } from 'react';
import {
View,
ScrollView,
TextInput,
} from 'react-native';
import CardView from 'react-native-cardview';
import styles from './styles';
export default class Signup extends Component {
render() {
return (
<View style={{ flex: 1, backgroundColor: colors.whiteColor }}>
<ScrollView contentContainerStyle={styles.signupContainer}>
<View style={styles.signupInputs}>
<CardView
style={styles.cardStyle}
cardElevation={2}
cardMaxElevation={2}
cornerRadius={5}
>
<TextInput
underlineColorAndroid="transparent"
style={[styles.signupInput, styles.commonsignupStyle]}
placeholder="Nom *"
placeholderTextColor={colors.primaryColor}
/>
</CardView>
<CardView
style={styles.cardStyle}
cardElevation={2}
cardMaxElevation={2}
cornerRadius={5}
>
<TextInput
underlineColorAndroid="transparent"
style={[styles.signupInput, styles.commonsignupStyle]}
placeholder="Prénom *"
placeholderTextColor={colors.primaryColor}
/>
</CardView>
</View>
</ScrollView>
</View>
);
}
}
Edit:
For dynamic height, two lines or more of text, as asked for in the comments, I had to use another workaround.
https://snack.expo.io/7bVXvbmE0
const Label = () => {
return <View style={{width: 100, height: 50}}>
<View style={styles.topView}>
<Text>Hello world</Text>
<Text>Hi world</Text>
</View>
<View style={styles.shadowView} >
<Text style={{color: 'transparent'}}>Hello world</Text>
<Text style={{color: 'transparent'}}>Hi world</Text>
</View>
</View>;
}
Whatever dynamic text you have on the label, duplicate for the shadow label, but make it transparent. That way you are guaranteed that the shadow follows the top view.
Also, get rid of the hardcoded heights in the styles. For both top view and shadow view, their heights are informed by the text input, and the wrapper container's height is informed by the two views.
Lastly, change shadow view style's top to be just a few points above 0 to make sure you it peeks from under topview. You can adjust borderRadius of the shadow view to fit your preferences.
const styles = StyleSheet.create({
topView: {
width: '100%',
position: 'absolute',
top: 0, backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 25,
},
shadowView: {
position: 'absolute',
top: 3,
width: '100%',
zIndex: -10,
borderRadius: 17,
backgroundColor: '#ddd'}
});
Previous Post
A little bit hacky, but you can do this if you absolutely don't want any blur.
https://snack.expo.io/pWyPplcm3
const Label = () => {
return <View style={{width: 100, height: 30}}>
<View style={styles.topView}>
<Text>Hello world</Text>
</View>
<View style={styles.shadowView} />
</View>;
}
styles:
const styles = StyleSheet.create({
topView: {
height: 25,
width: '100%',
position: 'absolute',
top: 0, backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 15,
},
shadowView: {
position: 'absolute',
top: 0,
height: 28,
width: '100%',
zIndex: -10,
borderRadius: 13,
backgroundColor: '#ddd'}
});

Pressing a button only dismisses the keyboard

I have a Modal which contains a TextInput and a Button. When the modal is opened, the keyboard appears which enables to enter text in the TextInput.
My problem is that when I press the button for the first time, the keyboard is dismissed but the button onPress method is not triggered.
import React from 'react';
import {
StyleSheet,
View,
Modal,
Text,
TextInput,
InteractionManager,
ScrollView,
TouchableWithoutFeedback,
TouchableOpacity,
Keyboard,
} from 'react-native';
import theme from '../../styles/theme.style';
import { MaterialCommunityIcons } from '#expo/vector-icons';
import I18n from '../../utils/i18n';
class RenameDrillModal extends React.Component {
// Enables to open the keyboard when the component is rendered
inputRef = React.createRef();
componentDidMount() {
this.focusInputWithKeyboard();
}
focusInputWithKeyboard() {
InteractionManager.runAfterInteractions(() => {
this.inputRef.current.focus();
});
}
render() {
return (
<View>
<Modal
animationType="fade"
transparent
visible
onRequestClose={() => {
this.props.close();
}}
>
{/* The sole purpose of the three following tags (TouchableOpacity, ScrollView, TouchableWithoutFeedback) is to close the modal when clicking outside of it */}
<TouchableOpacity
activeOpacity={1}
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}
onPressOut={() => {
this.props.close();
}}
>
<ScrollView directionalLockEnabled>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.modalTitleView}>
<Text style={styles.modalTitleText}>{`${this.props.drillTitle}`}</Text>
<View style={{ minWidth: '50%', flexDirection: 'row' }}>
<TextInput
style={{ minWidth: '90%', margin: 5, marginBottom: 0, flex: 1 }}
placeholder={I18n.t('drillEditor.drillManager.clickHereToRename')}
onChangeText={text => this.props.textModified(text)}
ref={this.inputRef}
/>
<MaterialCommunityIcons
name="check"
color={theme.COLOR_PRIMARY}
size={26}
onPress={() => {
console.log('Rename, onPress called');
this.props.close();
this.props.confirmNewName();
}}
/>
</View>
</View>
</TouchableWithoutFeedback>
</ScrollView>
</TouchableOpacity>
</Modal>
</View>
);
}
}
const styles = StyleSheet.create({
modalTitleView: {
margin: 20,
backgroundColor: 'white',
borderRadius: 20,
padding: 20,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
modalTitleText: {
fontWeight: 'bold',
fontSize: 20,
marginBottom: 15,
textAlign: 'center',
},
item: {
backgroundColor: theme.BACKGROUND_COLOR_LIGHT,
padding: 8,
borderRadius: 10,
marginVertical: 8,
marginHorizontal: 8,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
});
export default RenameDrillModal;
The second press on the button works but do you know how I could both dismiss the keyboard and push the button by only pressing once?

Know which element was touched with React Native's PanResponder?

Im making a drag and drop with the PanResponder but I only want part of a component to be the 'target' which initiates the drag. Many elements are contained within a ScrollView, and dragging on the elements outside of the 'target' should scroll as usual.
const Dragger = () => {
return (
<View {...panResponder.panHandlers}>
<View style={{ flexDirection: 'row' }}>
<Text>Drag me</Text>
<Text>Im not draggable</Text>
</View>
<View style={{ flexDirection: 'row' }}>
<Text>Drag me</Text>
<Text>Im not draggable</Text>
</View>
</View>
)
}
const Parent = () => {
return(
<ScrollView>
<Text>Stuff</Text>
<Dragger />
<Text>Stuff</Text>
<ScrollView/>
)
}
Can this be done or do I need to attach multiple panHandlers to each <Text>Drag me</Text>?
From the docs I see there is a nativeEvent.target which returns "The node id of the element receiving the touch event", however it's just a number so Im not sure how to use it?
Would I need to find out the node IDs of the <Text>Drag me</Text> elements and see if the number that came back is in that list?
UPDATE: Here is a more visual demo of what Im trying to do:
https://snack.expo.io/#jamesweblondon/rude-pretzel
I need PanResponder drag events on the grey boxes, but not on the text inputs:
import React from "react";
import { StyleSheet, Text, View, TextInput } from "react-native";
const items = new Array(50).fill(0).map((item, index) => {
return {
id: index,
backgroundColor: `rgb(${Math.floor(Math.random() * 255)}, ${
index * 5
}, ${132})`,
};
});
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
item: {
display: "flex",
flexDirection: "row",
height: 200,
width: "100%",
justifyContent: "space-around",
alignItems: "center",
},
input: {
color: "white",
fontSize: 20,
height: "100%",
flex: 1,
},
handle: {
display: "flex",
backgroundColor: "grey",
height: "80%",
width: 100,
marginHorizontal: 20,
},
});
export default function App() {
return (
<View style={styles.container}>
{items.map((item) => {
return (
<View
style={[styles.item, { backgroundColor: item.backgroundColor }]}
>
<View style={styles.handle} />
<TextInput style={styles.input} defaultValue={item.id} />
</View>
);
})}
<Text>Open up App.js to start working on your app!</Text>
</View>
);
}
You started your question with some snippets that I couldn't find in the link you posted, so I started from that and created a fork.
So in order to get the PanResponder drag events on the grey boxes, but not on the text inputs you should do like this on your item component:
<View style={[styles.item, { backgroundColor: item.backgroundColor }]} >
<View style={styles.handle} {...panResponder.panHandlers} />
<TextInput style={styles.input}>{item.id}</TextInput>
</View>
For the PanResponder i used the default config from https://reactnative.dev/docs/panresponder#example
Here's an update on your code to try it.
Hope this helps! Tell me if this isn't working for you.

Unable to position element to the right

I have a few elements on my app screen and I want the last one to be positioned towards the bottom-right (I'm using the Expo client on Android). Here's the render() function from App.js:
render() {
return (
<View style={styles.container}>
<StatusBar
hidden={true}
/>
<TodoItem/>
<TodoItem/>
<TodoItem/>
<AddTodoButton style={styles.button}/>
</View>
);
}
So, it's the <AddTodoButton\> that I want to position. Here's the styles I'm using:
const styles = StyleSheet.create({
container: {
backgroundColor: '#fcf7e2',
height: '100%',
},
button: {
position: 'absolute',
bottom: 0,
right: 0,
}
});
The code for AddTodoButton is as follows:
const Button = () => (
<Text style={styles.button}>+</Text>
);
const styles = StyleSheet.create({
button: {
fontSize: 30,
paddingLeft: 21,
paddingTop: 7,
width: 60,
height: 60,
backgroundColor: '#FF4456',
borderRadius: 60/2,
overflow: 'hidden',
}
});
I've tried several variations of styles for the button property, but the element doesn't respond and is stuck to the left edge right after the three <TodoItem/> (which are nothing but <Text> as of now).
I'm happy doing this in Flexbox, except that I don't know how to and managed to mess up the layout completely when I tried. :|
Any thoughts?
See if simply adding flex:1 to container style works, if not try the following
render() {
return (
<View style={styles.container}>
<StatusBar
hidden={true}
/>
<TodoItem/>
<TodoItem/>
<TodoItem/>
<View style={styles.buttonContainer}>
<AddTodoButton style={styles.button}/>
</View>
</View>
);
}
// In your styles, do the following
container: {
flex: 1
}
buttonContainer{
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'flex-end',
alignSelf: 'flex-end'
}
Not sure about your AddTodoButton but I'd recommend looking there to see if that's the one not pushing things to the right.
I think you just simply did not apply the style to your button:
const Button = (props) => (
<Text style={[styles.button,props.style]}>+</Text>
);
const styles = StyleSheet.create({
button: {
fontSize: 30,
paddingLeft: 21,
paddingTop: 7,
width: 60,
height: 60,
backgroundColor: '#FF4456',
borderRadius: 60/2,
overflow: 'hidden',
}
});

Touchablehighlight not clickable if position absolute

I have a Touchablehighlight that I need to position absolute, but it becomes unclickable after I do it.
What could cause this? It functions like it should if I dont have the position set to absolute.
Solution was to change the order of the components.
What i originally had:
<TouchableHighLight><Text>Click me</Text></TouchableHighlight>
<View> .... </View>
This was the fix:
<View>...</View>
<TouchableHighLight><Text>Click me</Text></TouchableHighlight>
Dude just go and add zIndex : 1 to the view containing the buttons and boom you are done in most of the cases. Also note adding elevation adds shadow to android button and sometimes elevation may also be a issue if its added to parent and not added to child then the child button may not work.(Rare Case)
eg:
buttonContainers:
{
zIndex: 1,
alignSelf: 'flex-end',
position: 'absolute',
top:5,
right: 5,
height: 40,
borderWidth: 1,
justifyContent: 'center',
alignContent: 'center',
width: 80
},
SOLVED:
I faced this issue today. I have solved it.
Import TouchableOpacity from react-native-gesture-handler instead of react-native.
Before:
import {TouchableOpacity} from "react-native";
After:
import {TouchableOpacity} from 'react-native-gesture-handler'
use onPressIn instead of onPress
That made the area clickable!
I used TouchableOpacity inside an absolute view. The onPress function was not called after press it. But the opacity changed. I've tried all the above solutions, but none works.
My solutions is use onPressIn instead of onPress.
It seems like the inner action of Touchable* is weird in ReactNative when it's in an absolute view.
After trying everything for two hours, the solution I found was to change my button position.
Before ...
export default class Navbar extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
return (
<View style={styles.content}>
<TouchableOpacity
onPress={this.props.openModal}
style={styles.containerButton}
>
<Text>New</Text>
</TouchableOpacity>
<Text style={styles.textCenter}>Remember me</Text>
</View>
);
}
}
const styles = StyleSheet.create({
content: {
paddingTop: 30,
paddingBottom: 10,
backgroundColor: '#81C04D',
flexDirection: 'row'
},
containerButton: {
position: 'absolute',
top: 30,
left: 8
},
textCenter: {
flex: 1,
textAlign: 'center',
fontWeight: 'bold'
}
});
After ...
export default class Navbar extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
return (
<View style={styles.content}>
<Text style={styles.textCenter}>Remember me</Text>
<TouchableOpacity
onPress={this.props.openModal}
style={styles.containerButton}
>
<Text>New</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
content: {
paddingTop: 30,
paddingBottom: 10,
backgroundColor: '#81C04D',
flexDirection: 'row'
},
containerButton: {
position: 'absolute',
top: 30,
left: 8
},
textCenter: {
flex: 1,
textAlign: 'center',
fontWeight: 'bold'
}
});
It works!!!
My solutions is:
style:{
zIndex: 1,
position: 'absolute',
}
use
zIndex: 1 in view, it'll work.
<View style={{position : 'absolute', marginTop : 25, zIndex: 1}}>
More details can be found here :
How to use zIndex in react-native
For me, it works like:
import { TouchableOpacity } from 'react-native';
onPress, zIndex: 1, position: 'absolute'
One more solution.....
For me what worked was a combination of things....
import { TouchableOpacity } from 'react-native-gesture-handler'
and I WRAPPED my TouchableOpacity in a View.
before:
<TouchableOpacity onPress={()=> addCallback()}
style={styles.addButtonHolder}
>
<PlusCircle style={styles.addButton} width={70} height={70} stroke={"white"} strokeWidth={3}/>
</TouchableOpacity>
after:
<View style={styles.addButtonHolder}>
<TouchableOpacity onPress={()=> addCallback()}>
<PlusCircle style={styles.addButton} width={70} height={70} stroke={"white"} strokeWidth={3}/>
</TouchableOpacity>
</View>
StyleSheet:
const styles = StyleSheet.create({
addButtonHolder: {
position: 'absolute',
bottom: 70,
right: 10,
justifyContent: 'center',
alignItems: 'center',
zIndex: 1,
},
addButton: {
backgroundColor: '#b4cffa',
borderRadius: 35
}
})
This worked for me
import { TouchableOpacity } from 'react-native-gesture-handler'
and changed onPress to onPressIn
<TouchableOpacity onPressIn={() => console.log('clicked')}></TouchableOpacity>
My solution was to import TouchableHighlight from 'react-native'
It was originally imported from 'react-native-gesture-handler'
This props help to disable ScrollView to catch all touches and let child handles
keyboardShouldPersistTaps='always'
'always', the keyboard will not dismiss automatically, and the scroll view will not catch taps, but children of the scroll view can catch taps.
'handled', the keyboard will not dismiss automatically when the tap was handled by children of the scroll view (or captured by an ancestor).
https://reactnative.dev/docs/scrollview#keyboardshouldpersisttaps
When the position is absolute, TouchableHighlight or TouchableOpacity goes beneath from the surface. You have to add a higher zIndex value to the Container.
He guy, I took a long time to find out why this happens.
I tested a lot of the solution here. Two things worked for:
The first answer here
The second one is to add elevation and zIndex on the wrapper container
<View style={{ zIndex:0 }>
...
<View style={{ position: 'absulote', zIndex:10 ,elevation: 10 }}>
<TouchableHighLight><Text>Click me</Text></TouchableHighlight>
</View>
<View> .... </View>
...
</View>
If I am right, the reason for that is even that the button is shown, Android treats differently the layers of the Press events and you need to set a low level for the rest of your components. Defining a lower level for your wrapper component, all its children without this attribute will automatically inherit from the parent.
My problem was quite different, a backgroundColor style property was set on the container of my button. The color didn't work. I missed to remove this useless property. Finally, this backgroundColor was making an invisible sheet above my button. Removing it make it clickable again.
I faced the problem only on Android.
Add a zIndex to the component where you added position absolute. This solved the issue for me.
position: 'absolute',
zIndex: 1,
`
I ran into a similar problem, what I did was, I enclosed the whole thing into a new View and instead of giving 'absolute' position to
the TouchableOpacity, I gave it to the parent View. That made the
Opacity again clickable somehow. Here is the code snippet of before
and after
My Code before
<TouchableOpacity
onPress={() => {
console.log("hah");
}}
style={{
height: 50, width: 50,
backgroundColor: 'rgb(90,135,235)',
borderRadius: 25, alignItems: 'center',
justifyContent: 'center', right: 0,position:'absolute'
}}>
<Image source={require('../assets/images/element-acorn-white.webp')}
style={{ height: 30, width: 30, resizeMode: 'contain' }} />
</TouchableOpacity>
After Wrapping into a View with 'absolute'
<View style={{
alignItems: 'flex-end', position: 'absolute',
bottom: Dimensions.get('screen').height / 5
}}>
<TouchableOpacity
onPress={() => {
console.log("hah");
}}
style={{
height: 50, width: 50,
backgroundColor: 'rgb(90,135,235)',
borderRadius: 25, alignItems: 'center',
justifyContent: 'center', right: 0,
}}>
<Image source={require('../assets/images/element-acorn-white.webp')}
style={{ height: 30, width: 30, resizeMode: 'contain' }} />
</TouchableOpacity>
</View>