FlatList onRefresh doesn't work with SafeAreaView - ios

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}
/>
}
/>
...

Related

React Navigation: How to put an iOS style dismissible bar on expo modal

I am trying to achieve a dismissible bar for my modal. Something like in this image:
What i am at right now
code:
<RootStack.Group
screenOptions={{
presentation: "modal",
gestureEnabled: true,
headerBackTitleVisible: false,
headerTitle: "",
hideNavigationBar: false,
gestureEnabled: true,
}}
>
<RootStack.Screen name="MyModal" component={ModalScreen} />
</RootStack.Group>
I am not sure if there is a built-in solution to that, but for sure you can hide the status bar per screen. In this case that's MyModal screen. And once you hide it you can implement a custom header that can be anything you wish it to be, but you will need to hook back actions etc.
Another way is to implement a custom Header, React Navigation has an API for that https://reactnavigation.org/docs/stack-navigator#header
Since there isn't a built in solution, you could achieve what you want by doing something like so:
(1) You will need to replace react-navigation's generated header with a custom one, like the following:
function LogoTitle() {
return (
<Image
style={{ width: 50, height: 50 }}
source={require('#expo/snack-static/react-native-logo.png')}
/>
);
}
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ headerTitle: (props) => <LogoTitle {...props} /> }}
/>
</Stack.Navigator>
);
}
Source: https://reactnavigation.org/docs/headers#replacing-the-title-with-a-custom-component
(2) Your custom component would have to look something like this:
export default function App() {
const cancelButtonOnPress = () => {
navigation.goBack();
}
return (
<View style={styles.container}>
<View style={styles.bar}></View>
<Text onPress={()=>{cancelButtonOnPress()}} style={styles.cancelButton}>Cancel</Text>
</View>
);
}
const styles = StyleSheet.create({
cancelButton: {
color: '#0573ad'
},
bar: {
alignSelf: 'center',
width: 40,
height: 7,
backgroundColor: 'gray',
borderRadius: 40,
},
container: {
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
borderWidth: 1,
borderColor: 'black',
},
});
The above App looks like the following:
(3) Since you are passing your custom component to react-navigation, the gesture swipe to dismiss the modal should be retained, if for some reason it isn't, you could try setting fullScreenGestureEnabled to true in screenOptions.

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).

TouchableOpacity is acting weird inside mapview in React Native

So i have 2 problems with TouchableOpacities inside my MapView:
The text and Icon inside my touchableopacities go outside of the touchableopacity, and in other components that doesn't happen.
One of the touchableopacities does nothing when clicked, it doesn't even change the opacity of the touchableopacity.
I haven't tried anything yet, except for revising my state and how it acts, and affects the elements, but nothing appears to be wrong.
this is my state:
this.state = {
lats: this.tempVars.homelat,
longs: this.tempVars.homelong,
latDs: [0.04],
longDs: [0.05],
numStores: this.tempVars.numStores,
StoreLats: this.tempVars.allLats,
StoreLongs: this.tempVars.allLongs,
StoreNames: this.tempVars.allNames,
createMarker: false,
placeMarker: false,
removeAmarker: false,
}
Helper Methods:
locChooser(){
if(this.state.createMarker){
console.log('Permanent Loc: '+JSON.stringify(this.MapLocs.permanentLoc));
return (
<Image style={{width: 65, height: 100, alignSelf: 'center', marginTop: Math.round(Dimensions.get('window').height/2)-150}} source={require('C:/Users/youse/FetcherApp/app/ping.png')}/>
);
}else if(this.state.placeMarker){
return (<Marker coordinate={this.MapLocs.permanentLoc} onPress={() => {if(this.state.removeAmarker){this.setState({placeMarker: false, removeAmarker: false})}}} image={{uri: 'https://i.pinimg.com/originals/30/98/49/309849c5815761081926477e5e872f1e.png'}}/>);
}else{
return null;
}
}
createHelpers(){
var bigarr = new Array(3);
if(this.state.createMarker){
bigarr[0] =
<View style={{flexDirection: 'column', justifyContent: 'space-between'}}>
<TouchableOpacity style={styles.DrawerOpener} onPress={() => this.props.navigation.dispatch(DrawerActions.openDrawer())}><Icon style={{marginTop: 20, marginLeft: 10}} name='reorder'/></TouchableOpacity>
<TouchableOpacity style={[styles.next, {borderColor: '#000000', backgroundColor: '#000000', marginLeft: 10}]} onPress={() => {this.setState({createMarker: false, placeMarker: true})}}><Text style={{color: '#fff', fontSize: 15}}>Select this Location</Text></TouchableOpacity>
</View>;
return bigarr[0];
}else{
bigarr[1] = <View key={0} style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<TouchableOpacity style={styles.DrawerOpener} onPress={() => this.props.navigation.dispatch(DrawerActions.openDrawer())}><Icon style={{marginTop: 20, marginLeft: 10}} name='reorder'/></TouchableOpacity>
<TouchableOpacity style={[styles.next, {borderColor: '#000000', backgroundColor: '#000000', marginLeft: 10}]} onPress={() => {this.setState({createMarker: true})}}><Text style={{color: '#fff', fontSize: 15}}>Choose a location to order from</Text></TouchableOpacity>
</View>;
bigarr[2] =
<View key={1}>
<TouchableOpacity style={[styles.next, {borderColor: this.BtnColor(!this.state.placeMarker), backgroundColor: this.BtnColor(!this.state.placeMarker), marginLeft: 10}]} disabled={!this.state.placeMarker} onPress={() => {console.log('Placemarker: '+this.state.placeMarker+', removeMarker: '+this.state.removeAmarker+', createmarker: '+this.state.createMarker);this.setState({removeAmarker: true})}}><Text style={{color: '#fff', fontSize: 15}}>Remove a Marker</Text></TouchableOpacity>
</View>;
return [bigarr[1], bigarr[2]];
}
}
And the render method:
render(){
return (
<MapView style={{flex: 2}} onRegionChange={this.__ChangeRegion} showsUserLocation={true} style={{position: 'absolute', left: 0, bottom: 0, right: 0, top: 0}} initialRegion={this.MapLocs.startloc}>
<View>
{this.createHelpers()}
</View>
{this.locChooser()}
{this.AllStores()}
<Marker image={require('C:/Users/youse/FetcherApp/app/home.png')} coordinate={this.MapLocs.homeLoc}/>
{this.displayCars(1)}
</MapView>
)
}
So I figured it out, it appears that components act very abnormally inside a Mapview, however when i nested it inside a View with a width and height of the screen dimensions, and put the components outside of the Mapview they acted normal again and they stayed in their styling and formatting.

React Native Button absolute position not working on real iOS specific device

I put a button in absolute position to on WebView.
This button works on any simulators and my iPhone 5s real device, but not work on iPhone Plus real device.
The button reacts when tapping, but doesn't fire onPress. It works when set position as relative.
All simulators and devices are same iOS version (12.2).
Does anyone know about this issue?
Environments
React Native 0.56
iOS 12.2
import React, { Component } from 'react';
import { ActivityIndicator, BackHandler, Keyboard, NetInfo, Platform, ScrollView, StatusBar, StyleSheet, Text, TouchableOpacity, View, WebView } from 'react-native';
import { Button, Icon } from 'react-native-elements'
export default class SignInScreen extends React.Component {
constructor(props) {
super(props)
}
handleBackPress() {
this.refs['WEBVIEW_REF'].goBack()
return true
}
render() {
return (
<View style={styles.container}>
<StatusBar backgroundColor="white" barStyle="dark-content" />
<WebView
ref="WEBVIEW_REF"
bounces={false}
source={{ uri: 'https://example.com' }}
style={styles.container}
/>
<Button
onPress={this.handleBackPress.bind(this) }
buttonStyle={styles.buttonBack}
title=""
icon={{
name: 'chevron-left',
type: 'material-community',
iconStyle: { color: global.color.normal }
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
buttonBack: {
width: 45,
height: 45,
position: 'absolute',
left: 10,
bottom: 60,
backgroundColor: '#bfbfbf',
opacity: 0.9
},
container: {
flex: 1,
paddingTop: 25,
backgroundColor: '#fff'
},
});
Position absolute does not work properly in some cases at ios. you should wrap in a :
<View style={{position:'absolute',...other styles}}>
<Button/> // without position style
<View>
Use padding on your button,it will solve.
You need to use Dimensions in this case. For example in your case you can use it like this:
buttonBack: {
width: Dimensions.get('window').width/100*10,
height: Dimensions.get('window').height/100*10,
position: 'absolute',
left: Dimensions.get('window').width/100*2,
bottom: Dimensions.get('window').height/100*5,
backgroundColor: '#bfbfbf',
opacity: 0.9
},
Dimensions.get('window').width/100*10 means 10 percent of total device
screen width and same for height.
Dimensions work as device specific. So it will take the same position according to Device Screen Height and Width.
Fixed by wrapping absolute position View.
render() {
return (
<View style={styles.container}>
<StatusBar backgroundColor="white" barStyle="dark-content" />
<WebView
ref="WEBVIEW_REF"
bounces={false}
source={{ uri: 'https://example.com' }}
style={styles.container}
/>
<View style={styles.navContainer}>
<Button
onPress={this.handleBackPress.bind(this) }
buttonStyle={styles.buttonBack}
title=""
icon={{
name: 'chevron-left',
type: 'material-community',
iconStyle: { color: global.color.normal }
}}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
buttonBack: {
width: 45,
height: 45,
left: 10,
backgroundColor: '#bfbfbf',
opacity: 0.9
},
// moved from buttonBack style
navContainer: {
position: 'absolute',
bottom: 60
},
//
container: {
flex: 1,
paddingTop: 25,
backgroundColor: '#fff'
},
});

Resources