I have a google map in react-native. On top of that View I have icons on the map, and these icons are placed on top of each other instead of being placed on either side of the map. I have tried many different settings but it always end up the same. Another thing is that the button reacts when I'm not clicking on it.
I have container and then mapview within the container. Here's the code:
container: {
flex: 1,
flexDirection: 'column'
},
map:{
height: 90+ "%",
flexDirection: 'column'
},
profileBtn:{
position: 'absolute',
top: 25,
right: 30,
width: 25,
height: 15,
borderRadius: 25/2,
},
homeBtn:{
position: 'absolute',
top: 20,
left: 5,
width: 200,
height: 100,
borderRadius: 25/2,
},
<MapView
style={styles.map}
initialRegion={{
latitude:this.state.latitude,
longitude:this.state.longitude,
latitudeDelta: 0.0043,
longitudeDelta: 0.0034
}}
ref={c => this.mapView = c}
onPress={this.onMapPress}
loadingEnabled={true}
>
<View style ={styles.topMap}>
<TouchableOpacity
style={styles.profileBtn}
onPress={()=>{ this.handleClickProfile() }}
>
<Image
source={profile}
borderRadius={17}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.homeBtn}
onPress={()=>{ this.handleClickFavourite() }}
>
<Image
source={require("./home.png")}
borderRadius={49}
/>
</TouchableOpacity>
</View>
{markers}
I'm not sure exactly why this would happen, but I read the source code from here, when you display a MapView, this gets returned:
return (
<AIRMap
ref={ref => { this.map = ref }}
{...props}
/>
)
Normally, when you write a custom view, you would do it this way:
return (
<AIRMap
ref={ref => { this.map = ref }}
{...props}
>
{children}
</AIRMap>
)
Maybe that's why your MapView's children don't get rendered the way you want it to. However, to solve the problem, you can simply move the views out of MapView, then wrap everything in a View:
return (
<View style={{ flex: 1 }}>
<MapView style={styles.map}>
{markers}
</MapView>
<TouchableOpacity ... />
<TouchableOpacity ... />
</View>
)
But remember, all those TouchableOpacity's must have position: 'absolute', or else your MapView won't take up the whole screen
The buttons are getting added one over the other because you have used position:absolute for the buttons.If you are intenting to use markers over map do it this way
import { Marker } from 'react-native-maps';
<MapView
region={this.state.region}
onRegionChange={this.onRegionChange}
>
{this.state.markers.map(marker => (
<Marker
coordinate={marker.latlng}
title={marker.title}
description={marker.description}
/>
))}
</MapView>
And visit this for more info
Related
I have a TextInput that when pressed gets covered by the keyboard. So I wrapped it in a KeyboardAvoidingView. But regardless of the behavior that I set for this view, the TextInput won't move above the keyboard. Using position as the behavior moves the TextInput but only half way above the keyboard, while the other two don't seem to work at all.
I also tried wrapping my entire component with a KeyboardAvoidingView, but doing so breaks the entire layout.
Can anyone help me? I never managed to get KeyboardAvoidingView to work for me and now I really need it. Thanks in advance!
Here is my component. Also worth mentioning is that this component is top level(well, almost top level since it's wrapped in a Router)
const { height, width } = Dimensions.get('screen')
const style = StyleSheet.create({
main: {
height,
width,
flexDirection: 'column',
},
iconSelecter: {
width,
height: 196,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: Colors.primary,
marginTop: 32
},
icon: {
height: 164,
width: 164,
},
saveButton: {
width: 96,
height: 96,
borderRadius: 100,
backgroundColor: Colors.secondary,
alignItems: "center",
justifyContent: "center",
alignSelf: 'center',
position: 'absolute',
bottom: 96 + 32
},
saveIcon: {
height: 54,
width: 54,
},
textInputWrapper: {
borderBottomColor: Colors.textInputBorder,
width: 288,
borderBottomWidth: 1,
alignSelf: 'center',
marginTop: 96,
height: 48,
},
textInput: {
fontWeight: "300",
fontSize: 14,
margin: 0
},
hintWrapper: {
alignSelf: 'center',
marginTop: 4
},
hint: {
fontSize: 12,
fontFamily: "Roboto-Thin",
fontStyle: 'normal',
}
})
const CreateActivity = ({ goBack }: NavigationProps) => {
//////////////////////////////
//State and logic
///////////////
return (
// TODO: Add touchable opacity to dismiss keyboard
<View style={style.main}>
<Appbar title="New activity" canGoBack goBack={goBack} />
<View style={{ flex: 1 }}>
<View style={style.iconSelecter}>
<GestureRecognizer onSwipeLeft={nextIcon} onSwipeRight={lastIcon}>
<Image style={style.icon} source={icons[currentIconIndex]?.file}></Image>
</GestureRecognizer>
</View>
<View style={style.hintWrapper}>
<Text style={style.hint}>Swipe to cycle through the icons</Text>
</View>
<KeyboardAvoidingView>
<View style={style.textInputWrapper}>
<TextInput style={style.textInput} placeholder={"Give this activity a name"} value={name} onChangeText={setName}></TextInput>
</View>
</KeyboardAvoidingView>
<TouchableNativeFeedback onPress={createActivity} background={TouchableNativeFeedback.Ripple("#fff", true)}>
<View style={style.saveButton}>
<Image style={style.saveIcon} source={require("../../assets/icons/light/save.png")}></Image>
</View>
</TouchableNativeFeedback>
</View>
</View>
)
}
export default CreateActivity;
I suggest that you to try wrap all the content of the screen in <KeyboardAvoidingView /> (or make it one of the outermost elements), otherwise it only will slide up its children (the View and the TextInput) leaving the rest of the content in its original position, making the layout look overlaped and weird. If you do that, the value "position" should work fine.
Something like this:
<View style={style.main}>
<Appbar title="New activity" canGoBack goBack={goBack} />
<KeyboardAvoidingView behavior="position" >
<View style={{ flex: 1 }}> // --> Remove flex: 1 if you experience some issue with the positioning
<View style={style.iconSelecter}>
<GestureRecognizer onSwipeLeft={nextIcon} onSwipeRight={lastIcon}>
<Image style={style.icon} source={icons[currentIconIndex]?.file}></Image>
</GestureRecognizer>
</View>
<View style={style.hintWrapper}>
<Text style={style.hint}>Swipe to cycle through the icons</Text>
</View>
<KeyboardAvoidingView>
<View style={style.textInputWrapper}>
<TextInput style={style.textInput} placeholder={"Give this activity a name"} value={name} onChangeText={setName}></TextInput>
</View>
</KeyboardAvoidingView>
<TouchableNativeFeedback onPress={createActivity} background={TouchableNativeFeedback.Ripple("#fff", true)}>
<View style={style.saveButton}>
<Image style={style.saveIcon} source={require("../../assets/icons/light/save.png")}></Image>
</View>
</TouchableNativeFeedback>
</View>
</KeyboardAvoidingView>
</View>
Also see the comment in the code above. Check if you really need to use of flex: 1 in all the outer wrapper elements, and take a look to the height you are setting in the style.main based on dimentions. I don't think that it is necesary and I think it could lead to some measure issues if you fix the height of the parent container.
EDIT:
I was just digging in react-native docs and I realize that there is a zIndex that you could use to avoid ablsolute positioning. It is a relative style prop so it needs to be set between sibling views, like this:
export default class MyComponent extends React.Component {
render() {
return (
<View>
<View style={[styles.appbarShape, styles.appbarZIndex]} ><Text>Header</Text></View>
<KeyboardAvoidingView behavior="position" style={styles.contentZIndex}>
{other children}
<TextInput placeholder="enter text"/>
</KeyboardAvoidingView>
</View>
);
}
}
const styles = StyleSheet.create({
appbarShape: {
height: 80,
width: Dimensions.get('window').width,
justifyContent: 'center',
alignSelf: "stretch",
backgroundColor: "#FFF"
},
appbarZIndex: {
zIndex: 3,
},
contentZIndex: {
zIndex: 0
}
});
Since the view that represents the appbar has a greater zIndex it shows up over the ones with a lower zIndex
Check this out working in this snack https://snack.expo.io/5VXAcw4Y0
Docs: https://reactnative.dev/docs/layout-props
Hope it helps!
Use react-native-keyboard-aware-scroll-view
<KeyboardAwareScrollView extraHeight={135} enabledOnAndroid={true}
extraScrollHeight={70} style={styles.mainContainer}
automaticallyAdjustContentInsets={true}
enableOnAndroid={true}
keyboardShouldPersistTaps='handled'
scrollEnabled={true} >
//your form
</KeyboardAwareScrollView>
const styles = StyleSheet.create({
mainContainer: { flex: 1, marginHorizontal: 15, marginVertical: 15 },
});
I am trying to place a floating action button in the lower right corner of my app but it is placing it in the top left way off screen.
Returned view:
<View>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity onPress={this.onPress} activeOpacity={.5} >
<Image
source={require('./assets/images/hamburger.png')}
style={{ width: 30, height: 25, marginLeft: 15}}
/>
</TouchableOpacity>
</View>
<FloatingAction style={styles.bottom}/>
</View>
Styles:
const styles = StyleSheet.create({
bottom: {
flex: 1,
position: 'absolute',
bottom: 10,
right:10
},
});
My current view displays a header and a bottom tab view. I am able to place multiple FAB's in each tab screen but that produces an undesirable behavior. Thank you for any help.
Edit:
What I have:
What I want:
Your issue was on adding { flex: 1, position: 'absolute',} to the button style together. The parent component that covers all the phone screen would use flex: 1, your button component is the one that receives the style for the position.
Always creating a new component makes stuff easier to read and understand. So let's say you have a button component (<FloatingButton/>), you would do something like this:
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import FloatingButton from './FloatingButton';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>
I'm just a Text
</Text>
<FloatingButton
style={styles.floatinBtn}
onPress={() => alert(`I'm being clicked!`)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
floatinBtn: {
position: 'absolute',
bottom: 10,
right: 10,
}
});
You will get this result:
This is the button component:
import React from 'react';
import { View, TouchableOpacity } from 'react-native';
export default props => (
<TouchableOpacity onPress={props.onPress} style={props.style}>
<View
style={{
backgroundColor: 'blue',
width: 45,
height: 45,
borderRadius: 45,
}}
/>
</TouchableOpacity>
);
Check the snack demo: https://snack.expo.io/#abranhe/floating-btn
// this should occupy the whole screen
<View style={{flex:1}}>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity onPress={this.onPress} activeOpacity={.5} >
<Image
source={require('./assets/images/hamburger.png')}
style={{ width: 30, height: 25, marginLeft: 15}}
/>
</TouchableOpacity>
</View>
<FloatingAction style={styles.bottom}/>
</View>
const styles = StyleSheet.create({
bottom: {
position: 'absolute',
bottom: 10,
right:10
},
});
Just use that CSS in your code:
.floating-btn {
position:fixed;
bottom:10;
right: 10;
}
That's it
The problem is that the last result from the search is hidden by the bottomTabNavigaor and I can't scroll the last result above it.
I need to see the bottomTabNavigaor not to hide it.
return (
<View>
<ScrollView style={{ backgroundColor: '#DEFEFC'}}>
{musicList.map(songObj => {
return (
<View key={songObj.Song_ID} >
<TouchableOpacity style={styles.resultsContainer}
onPress={this.GetListViewItem.bind(this, songObj.Song_ID)}
>
<Text style={{ fontSize: 16, flex:1}} key={songObj.Song_ID}>
{songObj.Song_Name}
</Text>
<Image source={{ uri: songObj.Image }} style={styles.img} />
</TouchableOpacity>
</View>
);
})}
</ScrollView>
</View>
);
my bottomTabNavigator:
const HomeStack = createStackNavigator({
Home: HomeScreen
});
HomeStack.navigationOptions = {
tabBarLabel: "Home",
tabBarIcon: (
<Image
style={{ width: 27, height: 27 }}
source={{uri: bottomImage+ "/home.png"}}
/>
)
};
const SearchStack = createStackNavigator({
Search: SearchScreen
});
SearchStack.navigationOptions = {
tabBarLabel: "Search Music",
tabBarIcon: (
<Image
style={{ width: 27, height: 27 }}
source={{uri: bottomImage +"/search_1.jpg"}}
/>
)
};
/////----{ I shorten it you can guess the others} ---
export default createBottomTabNavigator({
HomeStack,
ArtistsStack,
SearchStack,
PlaylistStack,
ShowDataStack
});
if any additional code is needed please tell me and I'll put it here.
This issue is not due to the navigator we had the same issue with the Scroll view the easiest way to sort it will be using a padding at the bottom for the scroll view.
<ScrollView style={{ backgroundColor: '#DEFEFC',paddingBottom: 50}}>
</ScrollView>
Update the padding value based on the height of your item.
When a user presses on a marker I am calling animateToCoordinate to center the marker in the middle of the screen. The problem is that the callout is rendered to the position of the region before animateToCoordinate is done its animation so the callout is displayed somewhat offscreen. is there anyway to delay callout display until animateToRegion is finished? Or is there another way to go about this entirely that I am not seeing?
<MapView
ref = {(mapView) => { _mapView = mapView; }}
style={styles.map}
region={this.state.mapRegion}
showsUserLocation = {true}
showsMyLocationButton = {true}
followUserLocation={true}
onRegionChange={this.onRegionChange.bind(this)}>
{data.near.map(marker =>
{if(marker.posts.length != 0){
return (
<MapView.Marker
coordinate={{latitude: marker.location.latitude,
longitude: marker.location.longitude}}
title={marker.name}
description={marker.description}
onPress={ e => _mapView.animateToCoordinate({
latitude: e.nativeEvent.coordinate.latitude,
longitude: e.nativeEvent.coordinate.longitude
}, 500)}
onCalloutPress={ e => this._onPressItem(marker.id)}
>
<Icon name="ios-pin" style={{ fontSize: 45, color: '#f04c34'}} />
<MapView.Callout
style={{ flex: -1, position: 'absolute', width: width}}>
<ListItem >
<Body >
<View style={{flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',}}>
<Text style={{fontSize: 18, color: '#38383a', marginBottom: 10, fontWeight: 'bold'}}
>{marker.name}</Text>
<Text style={{fontSize: 14, color: '#38383a', fontWeight: 'bold'}}>{marker.distance.toFixed(2)} miles</Text>
</View>
<Text style={{fontSize: 16, fontFamily: 'Avenir', marginBottom: 15}}
>{marker.infoText}</Text>
<Text>
<Text style={{fontSize: 12, color: '#38383a', fontWeight: 'bold'}}
>{marker.address}</Text>
</Text>
</Body>
<Right></Right>
</ListItem>
</MapView.Callout>
</MapView.Marker>
)
}else return null;
})}
</MapView>
I am having this precise issue as well. My current solution is to make my marker's onPress look something like this:
markerOnPress (coord) {
TIME_FOR_ANIMATION = 700;
this.mapview.animateToCoordinate(coord);
setTimeout(() => {
this.markerRef.showCallout();
}, TIME_FOR_ANIMATION);
}
And my marker looks like this:
<Marker
ref={(ref) => { this.markerRef = ref; }}
coordinate={coord}
title={someTitle}
description={someDescription}
onPress={() => this.onMarkerPress(coord)}
/>
This works alright, although there is some unwanted behavior still occurring and I'm not sure why. Now when I click on a marker, the callout appears immediately, then disappears before animating to the coordinate, animates to the coordinate, and then displays the callout as desired. I have no idea why it's still immediately displaying the callout initially. I tried fixing this by making the first line of markerOnPress this.markerRef.hideCallout() but this had no noticeable effect.
I found a make shift way to address this...
Using a state variable, you can hide/show a blank callout and the callout you want to show
{this.state.showCallout == true ? <Callout tooltip={true}>
<View style={{backgroundColor: 'white', width: 'auto', minWidth: 50, paddingVertical: 2}}>
{emp.type == "group" ?
<View>
{emp.employees.map(e => {
return (
<Text style={{marginVertical: 2}}>{e.display_name}</Text>
)
})}
</View>
:
<Text>
</Text>}
</View>
</Callout> : <Callout tooltip={true}>
<View style={{backgroundColor: 'transparent'}}></View>
</Callout>}
Then in onPress, you can setTimeout
this.mapRef.animateCamera({
center: {
latitude: emp.latitude,
longitude: emp.longitude,
},
heading: 0, pitch: 0,
zoom: 32,
altitude: 1000,
}, {duration: 1000});
setTimeout(() => {
this.setState({ showCallout: true })
this.markerRefs[i].showCallout();
},1000)
Then in onCalloutPress you can do
this.markersRefs[i].hideCallout();
this.setState({ showCallout: false )};
I have a ScrollView that has a top section with one background colour and a bottom section with another different colour.
When a user scrolls past the content and the view bounces (elastic over-extend), how could I make it so the background is consistent with either the top or the bottom, depending on the scroll direction?
I wouldn't play with the contentInset and contentOffset of the ScrollView as if your content changes, it might change the position of your scrollview.
You can do something very simple by just adding a View at the very top of your ScrollView:
// const spacerHeight = 1000;
<ScrollView>
{Platform.OS === 'ios' && (
<View
style={{
backgroundColor: 'red',
height: spacerHeight,
position: 'absolute',
top: -spacerHeight,
left: 0,
right: 0,
}}
/>
)}
</ScrollView>
On iOS, you can render a spacer View on top of the ScrollView, and use contentInset to render it "off-screen", contentOffset to set the initial scroll position to offset the inset:
render() {
const isIos = Platform.OS === 'ios'
const SPACER_SIZE = 1000; //arbitrary size
const TOP_COLOR = 'white';
const BOTTOM_COLOR = 'papayawhip';
return (
<ScrollView
style={{backgroundColor: isIos ? BOTTOM_COLOR : TOP_COLOR }}
contentContainerStyle={{backgroundColor: TOP_COLOR}}
contentInset={{top: -SPACER_SIZE}}
contentOffset={{y: SPACER_SIZE}}>
{isIos && <View style={{height: SPACER_SIZE}} />}
//...your content here
</ScrollView>
);
}
Because contentInset and contentOffset are iOS only, this example is conditioned to degrade gracefully on Android.
The accepted solution did not work well for me because I need to put flexGrow: 1 on the contentContainerStyle. Using insets/offsets didn't make the content grow the way I want, otherwise it worked not so bad.
I have another solution to suggest: putting a bicolor background layer under a transparent ScrollView, and add colors to your scrollview content. This way, on ios bounce, the bicolor layer under the scrollview will reveal itself.
Here's what I mean by bicolor layer (here the scrollview is empty and transparent)
Now if I put back the ScrollView children (which if a body with blank background, and a footer with yellow background), I get this:
As long as you don't bounce more than 50% of the scrollview height, you will see the appropriate background color.
Here's a component you can use to wrap your scrollview.
const AppScrollViewIOSBounceColorsWrapper = ({
topBounceColor,
bottomBounceColor,
children,
...props
}) => {
return (
<View {...props} style={[{ position: 'relative' }, props.style]}>
{children}
<View
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: -1, // appear under the scrollview
}}
>
<View
style={{ flex: 1, backgroundColor: topBounceColor }}
/>
<View
style={{ flex: 1, backgroundColor: bottomBounceColor }}
/>
</View>
</View>
);
};
And here's how you use it:
<AppScrollViewIOSBounceColorsWrapper
style={{flex: 1}}
topBounceColor="white"
bottomBounceColor="yellowLancey"
>
<ScrollView style={{flex: 1}}>
<WhiteBackgroundBody/>
<YellowBackgroundFooter />
</AppScrollView>
</AppScrollViewIOSBounceColorsWrapper>
Make sure to NOT set a background color to the scrollview, otherwise the bicolor layer will never reveal itself (backgroundColor on contentContainerStyle is fine)
This is, I think the most stupid simple way i found to do it:
<ScrollView style={{backgroundColor: '#000000'}}>
[...]
<View style={{position: "absolute", bottom: -600, left: 0, right: 0, backgroundColor: '#FFFFFF', height: 600}}/>
</ScrollView>
You may adjust the height/bottom absolute value to your likings depending on how far you think the user could scroll.
I personally implemented that into a <ScrollBottom color={"white"}/> component for ease of use in all my ScrollViews
For me, the simplest solution is modification based on Sebastien Lorber answer which doesn't include wrapping, just calling it before (or after) ScrollView component:
Create component:
interface IScrollViewBackgroundLayer {
topBounceColor: string;
bottomBounceColor: string;
}
export const ScrollViewBackgroundLayer = ({
topBounceColor,
bottomBounceColor,
}: IScrollViewBackgroundLayer): ReactElement => (
<View
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: -1, // appear under the scrollview
}}>
<View style={{ flex: 1, backgroundColor: topBounceColor }} />
<View style={{ flex: 1, backgroundColor: bottomBounceColor }} />
</View>
);
and use it like this:
return (
<SafeAreaView style={styles.container}>
<ScrollViewBackgroundLayer topBounceColor={topBounceColor} bottomBounceColor={bottomBounceColor} />
<ScrollView>
...
</ScrollView>
</SafeAreaView>