How to show callout in Airbnb maps? - react-native

I have a static map with one marker and want to always display the marker callout.
Some folks advise to use MapView's onLayout, but this doesn't work for some time now.
I'm attempting to solve it with onRegionChangeComplete which sometimes works (it gets called once or twice), but in about 10% cases it gets stuck in a loop and gets called again and again until the app crashes. This happens only on iOS.
This is my code
setMarkerRef = (ref) => {
this.marker = ref
}
showCallout = () => {
this.marker.showCallout()
}
render() {
...
<MapView
style={{...StyleSheet.absoluteFillObject}}
region={{
latitude: latitude,
longitude: longitude,
latitudeDelta: 0.00922,
longitudeDelta: 0.00421,
}}
showsMyLocationButton={false}
showsCompass={false}
showsTraffic={false}
zoomEnabled={false}
rotateEnabled={false}
scrollEnabled={false}
pitchEnabled={false}
onRegionChangeComplete={this.showCallout}
onPress={this.openMap}
>
<MapView.Marker
coordinate={{
latitude: latitude,
longitude: longitude,
}}
title={address}
ref={this.setMarkerRef}
/>
</MapView>
Any idea what's wrong with my approach? Does anyone have a better workaround?
Edit: I want the component to look like this, without the need for user interaction. Simply display a map, marker and callout.

Since you're rendering a fixed map with no intended region change, there's no need for you to listen to regionChangeComplete. It's really not a fun event to work with as its behaviour isn't that consistent.
initialRegion would work for showing your map. Or if you would like more fancy animation, trying using MapView animateToRegion.
Anyway, all you need to listen to is componentDidMount.
I suspect your refs code might be wrong, but anyway this works for me.
componentDidMount () {
this.refs['mymarker'].showCallout()
}
render () {
return (
<MapView>
<MapView.Marker
coordinate={YOUR_COORDINATES}
title='my callout'
ref='mymarker'>
</MapView.Marker>
</MapView>
)
}
If you still have problems, it could be that your title prop in MapView.Marker isn't receiving the address you are passing in when showCallout was called. In such a case, the call out doesn't render.
Updated Answer: (seemed like the above wasn't working for Apple Maps)
render () {
return (
<MapView
onLayout={() => this.refs["mymarker"].showCallout()}>
<MapView.Marker
coordinate={YOUR_COORDINATES}
title='my callout'
ref='mymarker'>
</MapView.Marker>
</MapView>
)
}

Related

React native maps not updating detail while zooming

I've implemented React Native Maps and I have a basic that allows me to interact with the map as expected.
I can pan North/East/South/West without a problem. But when I zoom, it doesn't update the finer details in the map like I'd expect.
My MapView is as follows:
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
initialRegion={initialRegion}
mapType={mapType}
onRegionChange={handleRegionChange}
onMapReady={() => {
setRegionReady(true)
}}
showsUserLocation={true}
followsUserLocation={true}
showsMyLocationButton={true}
/>
My initial region is:
const initialRegion = {
latitude: 56.55352329368351,
latitudeDelta: 10.777170227627643,
longitude: -4.6383764035999775,
longitudeDelta: 9.564308412373066,
}
Anything obvious I'm missing here?
Edit
This only seem to be an issue on simulators. Xcode/Android sims studio both show this behavior but physical devices seem to work.

react-native-maps callout content not updated on change

I am trying to update the content of a <Callout> tag on a google maps view using the react-native-maps npm package. I would like to dynamically load extra content after a map marker is pressed. I have simplified my code to the example below:-
I have already tried using the tracksInfoWindowChanges attribute on the marker, and the showCallout() method to attempt to manually trigger a re-render. I am using android so the iOS only options such as redrawCallout() method are not available.
const MyMap = (props) => {
const [marker, setMarker] = useState({});
return (
<MapView
showsUserLocation
zoomControlEnabled={true}
zoomEnabled={true}
region={{
latitude: 0,
longitude: 0,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}>
<Marker
coordinate={{latitude: 0, longitude: 0}}
onPress={(e) => {
setMarker({ title: 'Test'});
}}>
<View>
<Icon name='key' size={40} color='#384963'</Icon>
</View>
<Callout>
<Text>{marker.title}</Text>
</Callout>
</Marker>
</MapView>
);
}
Is it possible to trigger a re-render of the content after the Callout has been rendered once? From debugging I can see that the component renders after I update my marker state, however the content is not visible until I manually close the Callout and re-opened it.
I am having the exact same problem. The suggestion for android, as per this issue, is to use showCallout(). Below is my work-around for this issue using showCallout():
const MyMap = (props) => {
const [marker, setMarker] = useState({});
let markerRef = useRef(null);
useEffect(() => {
if (markerRef.current) {
markerRef.current.showCallout();
}
})
return (
<MapView
showsUserLocation
zoomControlEnabled={true}
zoomEnabled={true}
region={{
latitude: 0,
longitude: 0,
latitudeDelta: 0.03,
longitudeDelta: 0.03,
}}>
<Marker
ref={markerRef}
coordinate={{latitude: 0, longitude: 0}}
onPress={(e) => {
setMarker({title: `Test ${Math.floor(Math.random() * 10)}`});
}}>
<View>
<Icon name='key' size={40} color='#384963' />
</View>
<Callout>
<Text>{marker.title}</Text>
</Callout>
</Marker>
</MapView>
);
}
Briefly, I added a markerRef and call markerRef.current.showCallout() in useEffect(). What this achieves, if I am not mistaken, is that each time MyMap gets re-rendered, the callout will be forced to show again. I also changed the onPress such that the effect of callout re-rendering is more apparent.
Note 1: Each time the marker is pressed, the callout shall have its text updated.
Note 2: If you have multiple markers and callouts, and you want to update only the callout that is currently in view, you will have to add another check in useEffect(). Something like: if (markerRef.current && selectedMarker === 'markerID') to make sure that only the desired callout is re-rendered continuously.

React Native maps marker change destroys map state

I am using React Native maps, with real-time marker. This is my code.
const markers = useSelector(state => state.map.markers);
const animate = useCallback(() => {
if (map) {
map.animateToRegion({
latitude: location.latitude,
longitude: location.longitude,
latitudeDelta: location.latitudeDelta,
longitudeDelta: location.longitudeDelta,
});
}
}, [location, map]);
useEffect(() => {
if (location && isMapReady) {
animate();
}
}, [animate, location, isMapReady]);
const onMapLayout = () => {
setIsMapReady(true);
};
<MapView
ref={mapView => {
map = mapView;
}}
provider={PROVIDER_GOOGLE}
region={location}
onPress={() => {
animate();
}}
onMapReady={onMapLayout}>
{isMapReady &&
markers.length !== 0 &&
markers.map((marker, index) => (
<MarkerAnimated
key={index}
coordinate={{
latitude: marker.lat,
longitude: marker.lon,
}}
/>
))}
</MapView>
So whenever the state changes the markers get updated and the marker is changed in real-time in the maps.
But with one issue: the real-time comes in like 10 seconds, within that time, if I try to drag maps to somewhere or zoom in or out of the maps, whenever the marker state changes all these changes will be lost, the zoom will be reset to the old one, and I'll move back to the initial location, etc.
How can I fix this? I want to continue scrolling inside the map, zoom in and out, meanwhile, the markers need to get updated on the app.
This seems very late but if you set the region to initialRegion in your map view it fixes this problem.

React Native Maps ref issue

I have a react-native-maps MapView which is utilized in a fitToCoordinates function. The function zooms the map to fit a start point (current user location) and an end point. Below is the MapView object:
<MapView
ref={map => { this.map = map; }}
region={{
latitude: this.props.startLatitude,
longitude: this.props.startLongitude,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
}}
style={styles.mapContainer}
showsCompass={false}
showsUserLocation
>
<MapViewDirections
origin={{ latitude: this.props.startLatitude, longitude: this.props.startLongitude }}
destination={this.props.optimalDestination}
apikey={GOOGLE_API_KEY}
strokeWidth={2}
strokeColor={COLORS.MAIN.hexCode}
/>
{this.walkLine()}
{this.optimalAddressFound()}
{this.fitCoordinates()}
</MapView>
The fitToCoordinates function referenced in the MapView object is as follows:
fitCoordinates() {
if (this.props.optimalAddressFound && this.props.destinationMapFit) {
this.map.fitToCoordinates(
[{ latitude: this.props.startLatitude,
longitude: this.props.startLongitude },
{ latitude: this.props.optimalAddressLat,
longitude: this.props.optimalAddressLng }],
{ edgePadding: { top: 30, right: 30, bottom: 30, left: 30 } }
);
}
}
Everything works fine, except when a user opens the React Navigation Drawer Menu, navigates to another page, and then returns to the Map page. If they do that, the ref to the MapView object becomes undefined and the fitToCoordinates function fails (TypeError: Cannot ready property 'fitToCoordinates' of undefined).
My question is, how can I maintain the map ref if my user navigates to other screens? I have tried creating a function to store the MapView object in my Redux store but it seems that the Redux store cannot accept an object in the Reducer.
if you use react-navigation, you would need to set a key for your Map-Screen to make sure the same screen is reused instead of creating a new one each time.
something like
navigation.navigate({key: 'map-screen', routeName: 'Map'});

onRegionChangeComplete is called too many times, and not when really the event ends

This method is called when i not ended to pinch the map, is there anyway to fix this?
http://imgur.com/VtKeC1g
Here is the code
<MapView
style={{flex:1}}
region={{
latitude: parseFloat(this.state.initialPosition.coords.latitude),
longitude: parseFloat(this.state.initialPosition.coords.longitude),
latitudeDelta,
longitudeDelta,
}}
showsUserLocation={true}
onRegionChangeComplete={(region) => {
console.log('onRegionChangeComplete called!');
}}
/>
Use initialRegion in place of region and it will work fine.
Actually when you use region and onRegionChangeComplete together, onRegionChangeComplete works as a callback and gets triggered on every region change which again changes the region. Thus, it goes into an infinite loop.
Better use initialRegion with onRegionChangeComplete .
i justo solve it making a setTimeout! here is how
<MapView
style={{flex:1}}
showsUserLocation={true}
onRegionChange={(region) => {
if(this.timeout){
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => {
this.getDoctorsByGeolocation(region);
},1000);
}}
/>
i hope this can help anyone!