TouchableOpacity onPress doesn't work inside ScrollView - ios

(iOS Only)
<TouchableOpacity> doesn't respond if it is inside of a <ScrollView> :
It works properly in the simulator but not in a real device,
keyboardShouldPersistTaps="always" doesn't make any difference
Partial code:<ScrollView style={styles.scrollView}>
<TouchableOpacity style={styles.xButton} onPress={() => this._onClose()}>
any suggestions?
--- Code update -----
<ScrollView style={styles.scrollView}>
<TouchableOpacity style={styles.xButton} onPress={() => this._onClose()}>
<Image style = {styles.xImg} source = {require('../../images/xbtn.png')}/>
</TouchableOpacity>
{this._renderPricing()}
{this._renderServices()}
</ScrollView>
and the Styling looks like this:
scrollView:{
width: width,
height: height,
}, xButton: {
position: 'absolute',
zIndex: 1,
marginTop: '1%',
marginRight: '3%',
alignSelf: 'flex-end',
},xImg: {
resizeMode: 'contain',
aspectRatio: .6,
opacity: 0.5,
},

The issue was solved. It was caused because in my separate rendering methods the this._renderPricing etc I was changing the state too many times 👎 therefore the JS thread was occupied so the TouchableOpacity couldn't respond to touch events see RN documentation for a more detailed explanation if needed. Thank you very much for your answers.

Related

React Native - Scrollview scrolls up with many inputs [iOS]

I have many inputs within ScrollView. Everytime I try to tap the Save button, if the last input focused is out of the screen view (where the Save button is), the screen scrolls automatically up to the input. However, if I scroll down again and press/tap the Save button again, it works. So I have to tap twice the button.
This only happens on iOS devices. On Android works perfectly.
Here is my code:
render() {
const keyboardVerticalOffset = Platform.OS === "ios" ? 40 : 0;
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : ""}
keyboardVerticalOffset={keyboardVerticalOffset}
style={styles.container}
>
<View
style={{
flexDirection: "column",
justifyContent: "space-between",
height: "100%",
backgroundColor: colors.white,
}}
>
<ScrollView
ref={(scroll) => (this.scroll = scroll)}
keyboardShouldPersistTaps={'handled'}
style={{
marginBottom: this.state.marginBottom,
paddingHorizontal: 20,
}}
>
<DateForm
onChangeDate={this.onChangeDate.bind(this)}
date={this.state.measureDate}
birthdate={this.state.baby.Birthdate}
/>
<MeasureForm
onChangeMeasure={this.onChangeValue.bind(this)}
value={this.state.value}
scrollTo={this.scrollTo.bind(this)}
/>
<ResultsForm
IC={this.state.indexes.IC}
IABC={this.state.indexes.IABC}
colorIC={this.state.indexes.colorIC}
colorIABC={this.state.indexes.colorIABC}
/>
<CBButton
title={global.loc.getTranslation("AddMeasureSaveButton")}
style={{
width: "100%",
marginHorizontal: 0,
flexGrow: 1,
marginTop: 20,
marginBottom: 50,
}}
disabled={!this.state.validForm}
onPress={() => this.disableButton()}
/>
</ScrollView>
</View>
</KeyboardAvoidingView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
UPDATE
Here is a minimal reproducible example on snack

KeyboardAvoidingView not working properly in iOS

I'm having trouble getting my KeyboardAvoidingView to register properly on the iOS version of my react-native app. It's nested within another view in my ListFooterComponent of a Flatlist. The behavior is relatively normal on Android, but is not working at all on iOS.
Things I've tried:
Changing the behavior prop (all 3 props didn't end up working)
Changing the keyboardVerticalOffset prop (no amount of offset managed to change anything, it just adds a bunch of padding to the bottom of the screen)
Moving my KeyboardAvoidingView tag to the outermost view in ListFooterComponent. No difference
Adding flex: 1 to inner and outer components
I have seen other posts recommending using other keyboard avoidance libraries, but since this is an Expo Managed project, I don't think these will work for me. Any advice for how to accomplish this using the KeyboardAvoidingView component only?
This is a screenshot of the screen without a keyboard on iOS: this
And this is a screenshot of with a keyboard on iOS: this
And here's the code:
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data={commentData}
style={{ flex: 1 }}
keyboardDismissMode={'on-drag'}
keyboardShouldPersistTaps={'always'}
keyExtractor={item => (item.id)}
renderItem={({ item }) => {
return (
<>
</>
)
}}
ListFooterComponent={() => {
return (
<>
<View style={{ borderColor: "#D6DCE8", marginBottom: 0, borderTopWidth: 2, marginTop: 25, shadowColor: "#000", shadowRadius: 2, shadowOpacity: 0.25, shadowOffset: { width: 0, height: 2 }, elevation: 5 }} />
<View style={{ flex: 1, flexDirection: 'row', paddingTop: 15, paddingBottom: 15 }}>
<TouchableOpacity delayPressIn={20}>
<View style={styles.commentImage}>
<Image source={{ uri: myProfilePicture }} style={styles.image}></Image>
</View>
</TouchableOpacity>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.select({ ios: 100, android: 500 })}
style={{ flex: 1, backgroundColor: '#ECECEC', borderRadius: 10, flex: 0.95, paddingVertical: 5, marginLeft: 10 }}>
<TextInput style={{
fontSize: 14,
fontWeight: '500',
paddingHorizontal: 10,
marginHorizontal: 10,
paddingVertical: 6,
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap'
}}
ref={commentRef}
multiline={true}
blurOnSubmit={true}
numberOfLines={2}
onFocus={() => commentRef.current = true}
onBlur={() => commentRef.current = false}
placeholder="Leave a comment..."
defaultValue={comment}
onChangeText={(newValue) => { comment = newValue }}
onSubmitEditing={() => submitComment()} />
</KeyboardAvoidingView>
</View>
<View style={Platform.OS === 'android' ? { marginBottom: 200 } : { marginBottom: 100 }} />
</>
)
}}
ListHeaderComponent={() => {
return (
<>
</>
)
}}
/>
</SafeAreaView>
For the above layout, use KeyboardAvoidingView above FlatList in your hierarchy, and place the comment box not as the footer, but outside of the list component altogether.
Here's a snack with these changes (OP's code simplified for context): https://snack.expo.dev/#mlisik/thankful-tortilla
A few general notes:
padding behaviour should be what you want
keyboarVerticalOffset is useful if your view is nested inside a navigator (eg. from react-navigation), and navigation bar or tabbar are visible - you should then set the value to the height of those bars
disable it on Android (unless you have disabled keyboard/soft input handling in your manifest).

FlatList onRefresh doesn't work with SafeAreaView

Pull to refresh causes endless spinner and don't calling onRefresh when app tested on iPhone. On Android and iOS devices with home button everything works as expected.
ReactNative version: 0.58.3
When flex:1 removed from container style, everything works fine but it ruins a markdown of screen. Using ScrollView causes same problem.
render() {
return (
<View style={styles.container}>
<SafeAreaView style={styles.safeAreaView}>
<Toolbar
leftElement="menu"
centerElement="sometext"
style={{ container: { backgroundColor: '#ffa500' } }}
searchable={{
autoFocus: true,
placeholder: 'Search',
onChangeText: text => this.searchFilterFunction(text),
onSearchCloseRequested: () => this.resetSearchFilter()
}}
onLeftElementPress={() => this.props.navigation.openDrawer()}
/>
</SafeAreaView>
<FlatList
data={this.state.data}
keyExtractor={this.keyExtractor}
ItemSeparatorComponent={this.renderSeparator}
contentContainerStyle={{paddingLeft: '3%', paddingBottom: '4%'}}
refreshing={this.state.refreshing}
onRefresh={this.getData}
renderItem={({item}) =>
<PartnerCardComponent
partnerName={item.name}
discount={item.discount}
url={item.url}
image={item.image}
phone={item.telephones}
address={item.locations}
description={item.description}
navigation={this.props.navigation}
/>
}
/>
<SafeAreaView style={styles.bottomArea}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white'
},
safeAreaView: {
backgroundColor: '#ffa500',
shadowColor: 'gray',
shadowOffset: {height: 1, width: 0},
shadowOpacity: 0.5,
},
bottomArea: {
backgroundColor: 'white',
shadowColor: 'white',
shadowOffset: {height: -5, width: 0},
shadowOpacity: 5,
}
});
Expected: updating FlatList data
Receiving: endless spinner rotation, onRefresh doesn't calling.
I had a similar situation (though my FlatList was inside a SafeAreaView, not surrounded by them). What worked for me was using a, in my opinion, vaguely described RefreshControl component rather than setting the onRefresh and refreshing props directly. Without seeing the rest of your code (and importing RefreshControl from react-native) I've just dropped it in:
...
<FlatList
data={this.state.data}
keyExtractor={this.keyExtractor}
ItemSeparatorComponent={this.renderSeparator}
contentContainerStyle={{paddingLeft: '3%', paddingBottom: '4%'}}
refreshControl={<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.getData}
/>}
renderItem={({item}) =>
<PartnerCardComponent
partnerName={item.name}
discount={item.discount}
url={item.url}
image={item.image}
phone={item.telephones}
address={item.locations}
description={item.description}
navigation={this.props.navigation}
/>
}
/>
...

Why does adding a parent View Tag in React Native change the style?

Adding a View parent breaks my styles, why is this happening? Does React Native's View tag have some default styles that I'm unaware of?
Below is the Button component as well as a screenshot with and without the View Tag.
class Button extends Component {
render() {
return (
<View>
<TouchableOpacity style={styles.buttonStyle}>
<Text style={styles.textStyle}>Hello</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
textStyle: {
alignSelf: "center",
color: "#007aff",
fontSize: 16,
fontWeight: "600",
paddingTop: 10,
paddingBottom: 10
},
buttonStyle: {
flex: 1,
alignSelf: "stretch",
backgroundColor: "#fff",
borderRadius: 5,
borderWidth: 1,
borderColor: "#007aff",
marginLeft: 5,
marginRight: 5
}
});
The button is being rendered inside another component(A typical card component), if need I can provide that code as well.
Basically, your TouchableOpacity has all of your container styles, and this is going to cause the weird issue you are seeing. By defining flex:1, it means it will stretch itself to fill the area of the parent view. However, your parent view has not been flexed, so it will only fill the space that it needs to. It will however put your text component in however it seems fit, hence the weird result you are getting.
To debug this, I often will set backgroundColor: 'red', to see what flexbox is doing. In your case, i don't see the need for a parent view ? TouchableOpacity is simply an extension of view, so you should be able to do all your styling in it. If you are wanting to add a view though, i'd suggest wrapping it with your Touchable - otherwise you have the risk of messing up your onPresss. Something like
render() {
return (
<View style = {{height: 100, shadowColor: 'black', shadowRadius: 3}}> //RANDOM CARD CODE
<TouchableOpacity style={styles.buttonStyle}>
<View> //ANY STYLES HERE
<Text style={styles.textStyle}>Hello</Text>
</View>
</TouchableOpacity>
</View>
)};

React Native WebView rendering unexpected border at bottom

This is a little tricky to describe. I'm using a react native WebView component to make a mini-browser. It's working ok, but there's a strange black border at the bottom of it which I didn't put in. It appears when scrolling to or beyond the bottom of the webpage.
You can see the thin black line above the "blah" text below. The reason for the blah text and the pink section at the bottom was to rule out a stray borderBottom* style on the WebView or its container.
Another thing to note is that the border briefly disappears when reloading the page, so it seems it's in the WebView's html. But I don't understand how or why, because a) it appears on all websites and b) it doesn't appear in other iOS browsers.
Am I doing something strange here?
Screenshot:
Elements and Styles:
<View style={styles.container as any}>
<View style={styles.header as any}>
{this.addressbarButtonProps().map(props => React.createElement(Button, { ...props, key: props.title }))}
<TextInput
ref={ADDRESSBAR_REF}
placeholder="type url"
style={{ minWidth: this.screenWidth - 50 * this.addressbarButtonProps().filter(b => !b.hidden).length - 10 }}
/>
</View>
<WebView
ref={WEBVIEW_REF}
source={{uri: this.state.uri}}
style={{ ...styles.web, width: this.screenWidth, paddingBottom: 0, padding: 0 }}
scalesPageToFit={true}
allowsInlineMediaPlayback={true}
onMessage={m => this.onMessage(m.nativeEvent.data)}
injectedJavaScript={js}
startInLoadingState={true}
/>
<Text style={{ borderTopWidth:20, borderTopColor: "red" }}>blah</Text>
</View>
const styles = {
container: {
paddingTop: 20,
flex: 1,
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "#fafafa",
borderBottomColor: "pink",
borderBottomWidth: 100
},
header: {
flexDirection: "row",
alignItems: "flex-start",
justifyContent: "flex-start",
alignSelf: "stretch"
},
web: {
flex: 1,
borderBottomColor: "#fafafa",
backgroundColor: "#fafafa"
}
}
Set the backgroundColor of the WebView to transparent and the "border" won't be rendered.
<Webview style={{backgroundColor: 'transparent'}} .../>

Resources