Introduction
Expo is a fast an easy way to get started creating mobile apps with React Native, and all without needing MacOS to test on your iPhone. Used in conjunction with react-native-maps
, you can have an interactive Map up and running on a phone in no time.
Getting Started with Expo
First, install Expo globally with npm i expo -g
. Then, use expo init
to create an Expo project! Name your project, and then select "tabs (TypeScript)" under Managed workflow. Name your app.
Then run expo init
, install the Expo app on your mobile device, open camera, and scan the QR code you see in the terminal or web browser that opens up. You should see the template load up with two clickable tabs at the bottom.
Your First Map
Now run expo install react-native-maps
. This will install the latest react-native-maps
along with any dependencies for the current SDK (38.0 at the time of writing this blog post).
Now replace TabOnScreen.tsx with the following:
// TabOnScreen.tsx
import React, { useState } from 'react';
import { StyleSheet, Dimensions } from 'react-native';
import { View } from '../components/Themed';
import MapView from 'react-native-maps';
const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = 29.9990674;
const LONGITUDE = -90.0852767;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
export default function TabOneScreen({ provider }) {
const [region, setRegion] = useState({
latitude: LATITUDE, // initial location latitude
longitude: LONGITUDE, // initial location longitude
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
return (
<View style={styles.container}>
<MapView
provider={provider}
style={styles.map}
initialRegion={region}
zoomTapEnabled={false}
></MapView>
</View>
);
}
const styles = StyleSheet.create({
map: {
...StyleSheet.absoluteFillObject,
},
});
Here we created a simple MapView
component from react-native-maps
.
Maps with Markers and Custom Callouts
Now for an example with multiple different types of InfoWindows that pop up on Markers with information referring to that specific marker. In react-native-maps
, these are referred to as Callouts.
This will demonstrate three different types of Callouts that we can use with our Markers. Here, we have buttons to show and hide the Callout for the last selected Marker, and an overlay with just some explanatory text as well.
First, create new file called CustomCallouts.ts
and place the code from here into it.
Then, in TabTwoScreen.tsx
, place the following code below:
// TabTwoScreen.tsx
import React, { useState } from 'react';
import { StyleSheet, Dimensions, TouchableOpacity, Alert } from 'react-native';
import { Text, View } from '../components/Themed';
import MapView, { Marker, Callout, CalloutSubview } from 'react-native-maps';
import CustomCallout from './CustomCallout';
const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = 29.9990674;
const LONGITUDE = -90.0852767;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const SPACE = 0.01;
export default function TabTwoScreen({provider}) {
const [count, setCount] = useState(0);
const [region, setRegion] = useState({
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
const [markers, setMarkers] = useState([
{
coordinate: {
latitude: LATITUDE + SPACE,
longitude: LONGITUDE + SPACE,
},
},
{
coordinate: {
latitude: LATITUDE + SPACE,
longitude: LONGITUDE - SPACE,
},
},
{
coordinate: {
latitude: LATITUDE,
longitude: LONGITUDE,
},
},
{
coordinate: {
latitude: LATITUDE,
longitude: LONGITUDE - SPACE / 2,
},
},
]);
const [markerRefs, setMarkerRefs] = useState([
{
ref: null,
},
{
ref: null,
},
{
ref: null,
},
{
ref: null,
},
]);
const show = () => {
markerRefs[0].ref.showCallout();
};
const hide = () => {
markerRefs[0].ref.showCallout();
};
return (
<View style={styles.container}>
<MapView
provider={provider}
style={styles.map}
initialRegion={region}
zoomTapEnabled={false}
>
<Marker
ref={(ref) => {
let updateRef = markerRefs;
updateRef[0].ref = ref;
setMarkerRefs(updateRef);
}}
coordinate={markers[0].coordinate}
title="This is a native view"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation"
/>
<Marker coordinate={markers[1].coordinate}>
<Callout style={styles.plainView}>
<View>
<Text>This is a plain view</Text>
</View>
</Callout>
</Marker>
<Marker
coordinate={markers[2].coordinate}
calloutOffset={{ x: -8, y: 28 }}
calloutAnchor={{ x: 0.5, y: 0.4 }}
ref={(ref) => {
let updateRef = markerRefs;
updateRef[1].ref = ref;
setMarkerRefs(updateRef);
}}
>
<Callout
alphaHitTest
tooltip
onPress={(e) => {
if (
e.nativeEvent.action === 'marker-inside-overlay-press' ||
e.nativeEvent.action === 'callout-inside-press'
) {
return;
}
Alert.alert('callout pressed');
}}
style={styles.customView}
>
<CustomCallout>
<Text>{`This is a custom callout bubble view ${count}`}</Text>
<CalloutSubview
onPress={() => {
setCount(count + 1);
}}
style={[styles.calloutButton]}
>
<Text>Click me</Text>
</CalloutSubview>
</CustomCallout>
</Callout>
</Marker>
<Marker
ref={(ref) => {
let updateRef = markerRefs;
updateRef[3].ref = ref;
setMarkerRefs(updateRef);
}}
coordinate={markers[3].coordinate}
title="You can also open this callout"
description="by pressing on transparent area of custom callout"
/>
</MapView>
<View style={styles.buttonContainer}>
<View style={styles.bubble}>
<Text>Tap on markers to see different callouts</Text>
</View>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity
onPress={() => show()}
style={[styles.bubble, styles.button]}
>
<Text>Show</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => hide()}
style={[styles.bubble, styles.button]}
>
<Text>Hide</Text>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
customView: {
width: 140,
height: 140,
},
plainView: {
width: 60,
},
container: {
...StyleSheet.absoluteFillObject,
justifyContent: 'flex-end',
alignItems: 'center',
},
map: {
...StyleSheet.absoluteFillObject,
},
bubble: {
flex: 1,
backgroundColor: 'rgba(255,255,255,0.7)',
paddingHorizontal: 18,
paddingVertical: 12,
borderRadius: 20,
},
latlng: {
width: 200,
alignItems: 'stretch',
},
button: {
width: 80,
paddingHorizontal: 12,
alignItems: 'center',
marginHorizontal: 10,
},
buttonContainer: {
flexDirection: 'row',
marginVertical: 20,
backgroundColor: 'transparent',
},
calloutButton: {
width: 'auto',
backgroundColor: 'rgba(255,255,255,0.7)',
paddingHorizontal: 6,
paddingVertical: 6,
borderRadius: 12,
alignItems: 'center',
marginHorizontal: 10,
marginVertical: 10,
},
});
The first kind of Callout just has a simple Title and Description.
The second Callout uses the plain format, and only has a description, and has a darker pop-up window.
Third, we have a CustomCallout with a clickable button, and a default value of 0
. Let's observe what happens on button-click.
Ah, the magic of useState
! On press, the counter increments up by 1, and the value is now 1
.
And the same thing happens when we press the button again, the total is now 2
.
Conclusion
For further exploration with Markers in react-native-maps
, I recommend adding an image
tag to your Marker, as is done with this example. react-native-maps
is super responsive in Expo, and adding maps to your app is an easy way to impress. For further reading, check out the react-native-maps
documentation. Additionally, for further inspiration, look through the folder of examples on GitHub.
Top comments (0)