I'm transitioning over from react-native-maps to rnmapbox but am having issues getting any images to display on markers. I'm rendering an image marker and an additional badge for some markers. This issue only occurs when using an image, CSS markers work fine.
The image on the left is the expected result from when I was using React Native Maps, and the image on the right is the result with MapboxGL.
Here is a snippet of my code below:
<MapboxGL.PointAnnotation
key={"vehicle-" + vehicle._id}
id={"vehicle-" + vehicle._id}
coordinate={[vehicle.longitude, vehicle.latitude]}
anchor={
vehicle.status !== "riding" &&
vehicle.status !== "adminCollected" &&
vehicle.status !== "warehoused"
? { x: 0.5, y: 1 }
: { x: 0.5, y: 0.5 }
}
onSelected={() => centerOnMarker(vehicle)}
>
<View style={{ height: 75, width: 75 }}> {/* sized used for testing */}
{vehicle.status === "available" ? (
vehicle.batteryPercentage > 65 ? (
<Image
source={require("../../assets/scooter-pins/green-full.png")}
style={{ height: 54, width: 43.5 }}
/>
) : vehicle.batteryPercentage > 30 && vehicle.batteryPercentage < 66 ? (
<Image
source={require("../../assets/scooter-pins/green-medium.png")}
style={{ height: 54, width: 43.5 }}
/>
) : (
<Image
source={require("../../assets/scooter-pins/green-low.png")}
style={{ height: 54, width: 43.5 }}
/>
)
) : vehicle.status === "riding" ? (
<View style={{ ...styles.vehicleDot, backgroundColor: "#0096FF" }} />
)}
{vehicle.task &&
vehicle.status !== "riding" &&
vehicle.status !== "adminCollected" &&
vehicle.status !== "warehoused" && (
<View
style={{
...styles.vehicleTaskBadge,
backgroundColor:
vehicle.task.colour === "red"
? "#FF0000"
: vehicle.task.colour === "orange"
? "#FF8C00"
: "#D3D3D3",
}}
>
<Text
style={{
color: "#FFF",
fontWeight: "bold",
textAlign: "center",
fontSize: 12,
}}
>
{vehicle.task.priority}
</Text>
</View>
)}
</View>
</MapboxGL.PointAnnotation>
Any help would be appreciated as I've been spending the last few hours trying different things.
So turns out you need to re-render the marker once the image has loaded.
<MapboxGL.PointAnnotation
ref={ref => (this.markerRef = ref)}
key={"vehicle-" + vehicle._id}
id={"vehicle-" + vehicle._id}
coordinate={[vehicle.longitude, vehicle.latitude]}
onSelected={() => centerOnMarker(vehicle)}
>
<View style={{ height: 75, width: 75 }}> {/* sized used for testing */}
<Image
source={require("../../assets/scooter-pins/green-full.png")}
style={{ height: 54, width: 43.5 }}
onLoad={() => this.markerRef.refresh()}
/>
</View>
</MapboxGL.PointAnnotation>
Once the ref is added and the marker has refreshed, everything works well.
In my experience with Mapbox, the zoom level is usually to blame. Try zooming in to see if the images appear. If they do, you just need to set the zoom level you wish for the images to appear.
Related
I am trying to show multiple markers in Mapbox but there is nothing provided in the library about how to do it.
I have tried Mapbox.MarkerView and Mapbox.ShapeSource and Mapbox.PointAnnotation
When there is only one cordinate, it works fine. However, for more than one, the app crashes in both iOS and Android
I have tried this code
<MapboxGL.MapView
ref={(c) => (this._map = c)}
tintColor={'red'}
onLongPress={this.onLongPress}
onDidFinishLoadingMap={this.onDidFinishLoadingStyle}
logoEnabled={false}
compassEnabled={true}
style={{ flex: 1 }}>
<MapboxGL.Camera
ref={(c) => (this.map = c)}
zoomLevel={5}
centerCoordinate={CENTER_COORD}
/>
{/* <MapboxGL.MarkerView id={'hello'} coordinate={[73.20813, 22.29941]}> */}
{/* <MapboxGL.MarkerView
id={'hello'}
coordinate={[latitude, longitude]}> */}
{/* <View>
<Text>CYZ</Text>
</View>
</MapboxGL.MarkerView> */}
<MapboxGL.UserLocation
onUpdate={this.handleLocationUpdate}
renderMode='normal'
androidRenderMode='normal'
visible={true}
/>
{this.state.isFilterApplied === false
? this.state.recordValuesItemWithStatus.length > 0 &&
this.state.recordValuesItemWithStatus.map((data, index) => {
if (data[0] !== null && data.locationData[0] !== 'NaN') {
if (
this.isLatitude(data.locationData[0]) === true &&
this.isLongitude(data.locationData[1]) === true
) {
return (
<MapboxGL.PointAnnotation
ref={(ref) => (this.annotationRef = ref)}
coordinate={data.locationData}
key={index}
title={data.status}
onSelected={() => this.handleEditForm(data)}
style={{ zIndex: 1000 }}
id={index.toString()}>
<View
style={{
alignItems: 'center',
justifyContent: 'center',
}}>
<View
style={{
width: wp(5),
height: wp(5),
borderWidth: 1,
borderRadius: 40,
borderColor:
this.selectedColor[data.status.toUpperCase()],
backgroundColor:
this.selectedColor[data.status.toUpperCase()],
}}
/>
</View>
</MapboxGL.PointAnnotation>
);
}
}
})
: this.state.filteredRecordValues.length > 0 &&
this.state.filteredRecordValues.map((data, index) => {
if (data[0] !== null && data.locationData[0] !== 'NaN') {
if (
this.isLatitude(data.locationData[0]) === true &&
this.isLongitude(data.locationData[1]) === true
) {
return (
<MapboxGL.PointAnnotation
ref={(ref) => (this.annotationRef = ref)}
coordinate={data.locationData}
key={index}
title={data.status}
onSelected={() => this.handleEditForm(data)}
style={{ zIndex: 1000 }}
id={index.toString()}>
<View
style={{
alignItems: 'center',
justifyContent: 'center',
}}>
<View
style={{
width: wp(5),
height: wp(5),
borderWidth: 1,
borderRadius: 40,
borderColor:
this.selectedColor[data.status.toUpperCase()],
backgroundColor:
this.selectedColor[data.status.toUpperCase()],
}}
/>
</View>
</MapboxGL.PointAnnotation>
);
}
}
})}
</MapboxGL.MapView>
My app displays images and other info getting data from a JSON, and I want to set a blurRadius on the selected image when the unblur function is called.
My code is the following:
const [blur, setBlur] = useState(160);
const unblur = () => {
if(blur == 160){
setBlur(0);
}
}
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<>
<Text style={{ textAlign: "left", color: 'white', fontSize: 24 }}>
<Image style={{ alignSelf: "center" }} source={{uri: item.profile_picture, width: 48, height: 48}} />
{item.username}
</Text>
<TouchableOpacity onPress={() => unblur()}>
<Image style={{ alignSelf: "center" }} blurRadius={blur} source={{uri: item.picture, width: 400, height: 320}} />
</TouchableOpacity>
<Text style={{ textAlign: "left", color: 'white', fontSize: 24 }}>
ID {item.id}
</Text>
<Text>
{'\n'}{'\n'}
</Text>
</>
)}
/>
)}
I want to change the {blur} value for a given image when I tap the TouchableOpacity component of the image I want to unblur. Right in this moment when I tap an image, all the images in the list are getting unblurred.
You should add the blur values inside your items like this. Create a new components called Items. Then, add the blur state inside it. (not inside Main).
const Main = () => {
if (isLoading) {
return;
<ActivityIndicator />;
}
return (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => <Item item={item} />}
/>
);
};
const Item = ({ item }) => {
const [blur, setBlur] = useState(160);
const unblur = () => {
if (blur == 160) {
setBlur(0);
}
};
return (
<>
<Text style={{ textAlign: "left", color: "white", fontSize: 24 }}>
<Image
style={{ alignSelf: "center" }}
source={{ uri: item.profile_picture, width: 48, height: 48 }}
/>
{item.username}
</Text>
<TouchableOpacity onPress={() => unblur()}>
<Image
style={{ alignSelf: "center" }}
blurRadius={blur}
source={{ uri: item.picture, width: 400, height: 320 }}
/>
</TouchableOpacity>
<Text style={{ textAlign: "left", color: "white", fontSize: 24 }}>
ID {item.id}
</Text>
<Text>
{"\n"}
{"\n"}
</Text>
</>
);
};
Also, may I suggest you can use 'onPressIn'. That way, image will unblur when the finger tap the image, not press the image. But that was your decision.
I have a list in which I want to add an image.
Basically my code worked fine and the API database I use has unfortunately changed ... Some products in my database don't have an image so it gives me an error when I call the line in question...
So, I want to create a condition: If there is an image for the product in question in the database, I want it to be displayed
Image source={{uri: URL + item.photo.1.url}}
Otherwise I want it to be a preset logo.
<Image source={require('../../../assets/images/logo.png')}
I did it this way:
<ListItem.Content style={{flexDirection: 'row', justifyContent: 'space-between'}}>
{item.photo !== null && item.photo > 0 ? (
<Image
source={{uri: URL + item.photo._1_.url}}
style={{ width: 25, height: 25}}/>
) : (
<Image
source={require('../../../assets/images/logo.png')}
style={{ width: 25, height: 25}}
/>
)};
<ListItem.Title style={{width: '65%', fontSize: 16}}>{ ( item.name.length > 20 ) ? item.name.substring(0, 20) + ' ...' : item.name}</ListItem.Title>
<ListItem.Subtitle style={{ color: '#F78400', position: "absolute", bottom: 0, right: 0 }}>{item.cost}{i18n.t("products.money")}</ListItem.Subtitle>
</ListItem.Content>
But I have 2 errors:
undefined is not an object (evaluating 'item.photo.1.url')
[Unhandled promise rejection: Error: Text strings must be rendered
within a component.]
To show you how the data looks :
And the full code of that screen :
export default class Products extends Component {
constructor(props) {
super(props);
this.state = {
productId: (props.route.params && props.route.params.productId ? props.route.params.productId : -1),
listData: '',
selectedId: '',
setSelectedId: '',
currentPage: 1,
loadMoreVisible: true,
loadMoreVisibleAtEnd: false,
displayArray: []
}
};
initListData = async () => {
let list = await getProducts(1);
if (list) {
this.setState({
displayArray: list,
loadMoreVisible: (list.length >= 10 ? true : false),
currentPage: 2
});
}
};
setNewData = async (page) => {
let list = await getProducts(parseInt(page));
if (list) {
this.setState({
displayArray: this.state.displayArray.concat(list),
loadMoreVisible: (list.length >= 10 ? true : false),
loadMoreVisibleAtEnd: false,
currentPage: parseInt(page)+1
});
}
};
loadMore() {
this.setNewData(this.state.currentPage);
}
displayBtnLoadMore() {
this.setState({
loadMoreVisibleAtEnd: true
});
}
async componentDidMount() {
this.initListData();
}
render() {
//console.log('url', URL );
//console.log('displayArray', this.state.displayArray);
//console.log('name', this.state.displayArray.name);
//console.log('photo', this.state.displayArray.photo);
return (
<View style={{flex: 1}}>
{this.state.displayArray !== null && this.state.displayArray.length > 0 ? (
<View style={{ flex: 1}}>
<SafeAreaView>
<FlatList
data={this.state.displayArray}
extraData={this.selectedId}
style={{width: '98%'}}
onEndReached={() => this.displayBtnLoadMore()}
renderItem={({item, index, separators })=>
<View style={{flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center'}}>
<ListItem
style={{width:'100%'}}
containerStyle= {{backgroundColor: index % 2 === 0 ? '#fde3a7' : '#FFF'}}
bottomDivider
onPress={() => this.props.navigation.navigate('ProductDetails', {productId:parseInt(item.id)})}>
<ListItem.Content style={{flexDirection: 'row', justifyContent: 'space-between'}}>
{item.photo !== null && item.photo > 0 ? (
<Image
source={{uri: URL + item.photo._1_.url}}
style={{ width: 25, height: 25}}/>
) : (
<Image
source={require('../../../assets/images/logo.png')}
style={{ width: 25, height: 25}}
/>
)};
<ListItem.Title style={{width: '65%', fontSize: 16}}>{ ( item.name.length > 20 ) ? item.name.substring(0, 20) + ' ...' : item.name}</ListItem.Title>
<ListItem.Subtitle style={{ color: '#F78400', position: "absolute", bottom: 0, right: 0 }}>{item.cost}{i18n.t("products.money")}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
</View>
}
keyExtractor={(item,index)=>index.toString()}
style={{width:"100%"}}
/>
{this.state.loadMoreVisible === true && this.state.loadMoreVisibleAtEnd === true ? (
<Button title=" + " onPress={()=>{this.loadMore()}}></Button>
) : null
}
<View style={styles.container}>
<Text>{"\n"}</Text>
<TouchableOpacity
style={styles.touchable2}
onPress={() => this.props.navigation.goBack()}
>
<View style={styles.container}>
<Button
color="#F78400"
title= 'Back'
onPress={() => this.props.navigation.goBack()}>BACK
</Button>
</View>
</TouchableOpacity>
</View>
<Text>{"\n\n"}</Text>
</SafeAreaView>
</View>
) : (
<View style={styles.container}>
<Text>{"\n\n" + (this.state.displayArray === null ? i18n.t("products.searching") : i18n.t("products.nodata")) + "\n\n\n"}</Text>
<Button
color="#F78400"
title= 'Back'
onPress={() => this.props.navigation.goBack()}>BACK
</Button>
</View>
)}
</View>
);
};
}
I'm a little lost in how to do it, so if you have any clue to help me, any lead it would be great. Thanks a lot
This test:
item.photo !== null && item.photo > 0
Will not return what you expect. The reason is that when there is no photo, the property is set to an empty string. So the first part of that test should be:
item.photo !== ''
Next, when there is a photo, the photo property is an object. So the second part should be:
item.photo.constructor === 'Object'
But, that will be compounded if there are more than one photos. Your code suggests you only want the first photo (regardless of how many there may be).
So if you make the changes I've suggested, it should work as you expect.
If I were you, I would skip the first test altogether as it isn't necessary now that the second test covers both cases. I recommend just doing this:
{item.photo.constructor === 'Object' ? (
I want it to play only when the card is selected, if the card is not selected it simply displays the lottie file but nothing is being played. Right now I get an error of cannot read property 'play' of null. When I remove the animationPress() and get the file to run it also start at the mentioned initialSegment that I'm passing into it.
What am I doing wrong here?
My card that the Lottie file is in:
const animation = useRef(null);
const animationPress = () => {
animation.current.play();
}
return(
{filteredData.map((item, index) => {
return (
<>
<TouchableOpacity onPress={() => setSelected(item), animationPress()}>
<View style={[{ alignItems: 'center' }, selected.id == item.id ? { paddingTop: 10 } : { paddingTop: 20 }]}>
<CardioCard style={selected.id == item.id ? { backgroundColor: 'green', fontFamily: 'ssb_SemiBold' } : {}} >
<CardItem style={[styles.card, selected.id == item.id ? { backgroundColor: 'green' } : {}]}>
<Text>
< LottieView
ref={animation}
source={require('../../assets/images/heartbeat.json')}
initialSegment={68,135}
autoPlay={true}
loop={true}
style={{ width: 100, height: 100 }} />
</Text>
</CardItem>
</CardioCard>
<Text style={[{ fontSize: 18, color: 'hsl(98, 0%, 11%)', paddingLeft: 15 }, selected.id == item.id ? { fontFamily: 'ssb_SemiBold' } : {}]}>{item.name}</Text>
</View>
</TouchableOpacity>
</>
)
})}
...
)
Edit
I also tried to do useState for the autoplay and loop button so they would be false but when the button is pressed it would turn true but the animation wouldn't load even after the button press.
const [playAnimation, setPlayAnimation] = useState(false);
<TouchableOpacity onPress={() => setPlayAnimation(true)}>
<LottieView
source={require('../../assets/images/heartbeat.json')}
autoPlay={playAnimation}
loop={playAnimation}
style={{ width: 100, height: 100 }}
/>
</TouchableOpacity>
this is driving me nuts and idk what to do anymore.
I'm new to react-native but I guess it's like react. Not sure to correctly understand the issue.
In this case, shouldn't you use animation.current instead of animation as your lottieView ref? or at least ensure that your ref animation is defined (not null) so that the container can be mounted even if not launched, this is also for your animationPress function. Also you should disable the autoPlay I guess (since you want to trigger the animation with a button).
< LottieView
ref={animation.current}
source={require('../../assets/images/heartbeat.json')}
initialSegment={68,135}
autoPlay={false}
loop={true}
style={{ width: 100, height: 100 }} />
or
{animation && < LottieView
ref={animation}
source={require('../../assets/images/heartbeat.json')}
initialSegment={68,135}
autoPlay={false}
loop={true}
style={{ width: 100, height: 100 }} />}
And of course
const animationPress = () => {
if(animation?.current) {
animation.current.play();
}
}
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 )};