Codementor Events

Building an iOS Geolocation Travel App with React Native

Published Oct 12, 2016Last updated Jan 18, 2017
Building an iOS Geolocation Travel App with React Native

I. Introduction

From travelers planning their trips to regular commuters who want to keep track of all the places they go to, keeping track of where people have been to, where they are, and where they want to go are features users look for in a light-weight, user-friendly travel app.

An app that manages and keeps track of favorite places and routes is something you can build using React Native!

II. Background

A. React Native

React Native is a framework for building mobile apps using React and JavaScript. It has support for building mobile apps for the iOS and Android platforms. The framework is open-sourced by Facebook on March 2015. It is built on the premise:

Learn once, Write anywhere: Build mobile apps with React.

React Native has a set of inbuilt React components. For example, there are components such as TabBar, Navigator, Switch, DatePicker, and MapView. These components get translated to native iOS or Android components. The layout of the components is controlled by an implementation of Flexbox (CSS). Styles specified by props on the React Native component are translated to styles on the native UIKit component. In this way, React Native wraps the UIKit on iOS just as React wraps the DOM on web.

B. React

React allows building UI components. UI components are composed of layout, style, and JavaScript. For web applications, the layout is translated to HTML and style is translated to CSS.

III. Create the app

In this tutorial, we will build a sample location-based travel app using React Native. The tutorial assumes that you have some knowledge of working with React and JavaScript.

Our sample app allows the user to view favorite places on a map in the neighborhood. There are two tabs in the app: Places tab and Add Place tab. The Places tab shows the favorite places in a MapView component. The Add Place tab adds a place to the Map. When clicking on one of the favorite places on the map, the user is taken to Maps with driving directions.

We will build the sample app for the iOS platform.

A. Getting Started

The Getting Started page has detailed instructions on how to install React Native on your system. For a Mac, install Node and Watchman.

brew install node
brew install watchman

React Native is a node package that is installed via npm.

sudo npm install -g react-native-cli

To create our Places project, use the init command from react-native.

react-native init Places

To run the app in iOS simulator, use the run-ios command from react-native within the project folder.

cd Places
react-native run-ios

The iOS simulator is started and our project is built. A new terminal window opens up that listens on port 8081 and provides the script bundle. When an app is deployed to the AppStore, the script bundle is either embedded within the app or provided by a service such as AppHub. The built app is installed in the simulator and opened. The screenshot of the initial project is shown below.

screenshot of initial project

B. Tabbed navigation

The entry-point for the iOS app is index.ios.js file. Any modification to the file will reflect in the iOS simulator when the app is reloaded. To reload an app in the simulator, use Command-R or the Shake Gesture from the simulator menu.

TabBarIOS is a React Native component. Using the component provides tabs at the bottom of the view. Along with the TabBarIOS, we will import the Textand Stylesheetcomponent.

import {
  AppRegistry,
  Text,
  TabBarIOS,
  StyleSheet
} from 'react-native';

TabBarIOS component contains items of TabBarIOS.Item. Each item is a tab with a title and an icon. The item also has a selected property. When the tab is selected, it is tinted. The icon can be a system icon. When a system icon is used, the title is provided by the system icon. We will create tabbed navigation with two tabs as follows.

<TabBarIOS>
  <TabBarIOS.Item
    systemIcon="favorites"
    selected={true}
  >
    <View>
      <Text>Favorite Places</Text>
    </View>
  </TabBarIOS.Item>
  <TabBarIOS.Item
    systemIcon="more"
  >
    <View>
      <Text>Add Place</Text>
    </View>
  </TabBarIOS.Item>
</TabBarIOS>

Within our tabs, we place the child component. The text component is displayed in the view when a tab is selected. The Text component is displayed in the top left corner of the view. We will style the component using the StyleSheet component.

const styles = StyleSheet.create({
  text: {
    textAlign: 'center',
    color: '#333333',
    marginTop: 50,
  },
  view: {
    backgroundColor: '#fed',
    flex: 1
  }
});

The text style defined above gives the text some color, aligns the text to the center, and moves the text 50 pixels from the top of the view. The view is styled with the background color. The style of flex: 1 makes a Flexbox which fills the entire screen. Styles are attached to the component just as for any other React component.

<View style={styles.view}>
  <Text style={styles.text}>Favorite Places</Text>
<View>

For the second tab, we will use a custom icon and a title. Image files are typically stored in an assets folder. The icon size is of 32 x 32. The image for pin.png is:ios geolocation app

<TabBarIOS.Item
  title="Place"
  icon={require('./assets/pin.png')}
>
</TabBarIOS.Item>

When the user clicks on a tab, we want to highlight the tab. Highlighting the tab is done by changing the selected prop of the corresponding tab to true. To accomplish this, we define the selectedTab state. The selectedTab is initially set to zero which corresponds to the first tab.

constructor() {
  super();
  this.state = {
    selectedTab: 0
  };
}

To change the selectedTab, we handle the onPress event of the TabBarIOS.Item.

handleTabPress(tab) {
  this.setState({ selectedTab: tab })
}

Finally, we use the selectedTab state to update the selected prop of the Tab. The complete JSX is shown for clarity.

<TabBarIOS>
  <TabBarIOS.Item
    systemIcon="favorites"
    selected={this.state.selectedTab === 0}
    onPress={this.handleTabPress.bind(this, 0)}
  >
    <View style={styles.view}>
      <Text style={styles.text}>Favorite Places</Text>
    </View>
  </TabBarIOS.Item>
  <TabBarIOS.Item
    title="Place"
    icon={require('./assets/pin.png')}
    selected={this.state.selectedTab === 1}
    onPress={this.handleTabPress.bind(this, 1)}
  >
    <View style={styles.view}>
      <Text style={styles.text}>Add Place</Text>
    </View>
  </TabBarIOS.Item>
</TabBarIOS>

The screenshot of the iOS simulator after adding tabs is shown below.

After adding tabs

C. Map of favorite destinations

In this section, we will create a map and populate it with our favorite destinations. These destinations are the frequently visited places in the neighborhood.

Create a new PlaceMap component in a file named place_map.js. The PlaceMap component will use the MapView component of React Native. The MapView component is designed only for iOS platform. The initial code for PlaceMap component is shown below.

import React, { Component } from 'react';
import {
  MapView,
  View,
  StyleSheet
} from 'react-native';

export default class PlaceMap extends Component {
  render() {
    return (
      <MapView 
        style={styles.map}
      />
    );
  }
}

const styles = StyleSheet.create({
  map: {
    flex: 1
  }
});

The MapView component is allowed to flex and occupy the entire screen. The MapView component should be provided with the initial region. The region is set via props. The region has five properties: latitude, longitude, latitudeDelta (zoom), longitudeDelta, and title.

<MapView
  style={styles.map}
  region={{
    latitude: 38.8977,
    longitude: -77.0365,
    latitudeDelta: 0.2,
    longitudeDelta: 0.2,
    title: "White House"
  }}
/>

The Map is anchored with White House at the center. To view the map, the PlaceMap component should be placed within the first tab as follows.

<TabBarIOS.Item
  systemIcon="favorites"
  selected={this.state.selectedTab === 0}
  onPress={this.handleTabPress.bind(this, 0)}
>
  <PlaceMap />
</TabBarIOS.Item>

Our favorite destinations are stored as annotations on the map. Annotation has a title, latitude and longitude. We will define the annotations on the state of the main component Places.

constructor() {
  super();
  this.state = {
    selectedTab: 0,
    annotations: [
      {
        title: 'Smithsonian Museum',
        latitude: 38.8980,
        longitude: -77.0230
      },
      {
        title: 'UMCP',
        latitude: 38.9869,
        longitude: -76.9426
      },
      {
        title: 'Arlington',
        latitude: 38.8783,
        longitude: -77.0687
      }
    ]
  };
}

The annotations are supplied to the PlaceMap via props.

<PlaceMap annotations={this.state.annotations} /> 

The annotations are further passed on to the MapView component. The MapView component displays the annotations as markers on the map. Clicking on one of the markers will show the title.

<MapView
  style={styles.map}
  region={this.region}
  annotations={this.props.annotations}
/>

The screenshot of the simulator after adding the MapView component is shown below.

After adding MapView

D. Form for adding places

Users interact with an app using various input components:

  • The TextInput component accepts text input.
  • The TouchableHighlight component responds to user touches and works like a button.
  • Other components for accepting user input are Picker, Slider and Switch component.

In this section, we will add a form with three TextInput components and a TouchableHighlight component. The TextInput components correspond to Title, Latitude and Longitude of a place. The initial code for the AddPlace component is shown below.

import React, { Component } from 'react';
import {
  Text,
  TextInput,
  TouchableHighlight,
  View,
  StyleSheet
} from 'react-native';

export default class AddPlace extends Component {
  render() {
    return (
      <View style={styles.view}>
        <Text style={styles.text}>Title</Text>
        <TextInput
          style={styles.textInput}
        ></TextInput>
        <Text style={styles.text}>Latitude</Text>
        <TextInput
          keyboardType="numbers-and-punctuation"
          style={styles.textInput}
        ></TextInput>
        <Text style={styles.text}>Longitude</Text>
        <TextInput
          keyboardType="numbers-and-punctuation"
          style={styles.textInput}
        ></TextInput>
        <TouchableHighlight style={styles.button}>
          <Text style={styles.buttonText}>Add Place</Text>
        </TouchableHighlight>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  view: {
    paddingTop: 50,
    paddingLeft: 30,
    paddingRight: 30,
    backgroundColor: '#fed',
    flex: 1
  },
  text: {
    color: '#333333',
    marginBottom: 5
  },
  textInput: {
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    marginBottom: 5
  },
  button: {
    backgroundColor: '#ff7f50',
    padding: 12,
    borderRadius: 6
  },
  buttonText: {
    color: '#fff',
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center'
  }
});

Handling the keyboard is a bit of a challenge. If you look closely, latitude and longitude have numeric input. The keyboardType of the component can be set to numeric to enable numeric input. But, numeric input does not allow negative numbers. Both latitude and longitude can be negative. To allow negative numbers, the keyboardType should be set to numbers-and-punctuation.

When the button in the AddPlace component is pressed, the place should be added to the map. We will go back to the AddPlace component to add that functionality. The three TextInput components are mandatory for the user to fill out. If there is no text, an error message should appear. The text value and error message should be stored in the state. Initialize the state in the constructor.

constructor() {
  super();
  this.state = {
    title: '',
    latitude: '',
    longitude: '',
    titleError: '',
    latitudeError: '',
    longitudeError: ''
  };
}

The TextInput component should be a controlled component. The value is derived from the state. The onChangeText event is handled to set the corresponding state.

<TextInput
  style={styles.textInput}
  value={this.state.title}
  onChangeText={(title) => this.setState({ title })}
>
</TextInput>

The onPress event of the TouchableHighlight component should be handled. The event handler checks if all the fields are filled out. It displays the error messages, if any. If there are no error messages, the location is added to the map.

handleAddPlace() {
  const { title, latitude, longitude } = this.state;
  let titleError = '';
  let latitudeError = '';
  let longitudeError = '';
  if (!title) {
    titleError = 'Name is required.';
  }
  if (!latitude) {
    latitudeError = 'Latitude is required.';
  }
  if (!longitude) {
    longitudeError = 'Longitude is required.';
  }

  this.setState({
    titleError,
    latitudeError,
    longitudeError
  });

  const isError = titleError || latitudeError || longitudeError;
  if (!isError) {
    this.props.onAddPlace({
      title,
      latitude: parseFloat(latitude),
      longitude: parseFloat(longitude)
    });

    AlertIOS.alert(
      'Place added',
      'Your place is added to the map. Click on the Favorites tab to view.'
    );
  }

  dismissKeyboard();
}

The AlertIOS component is part of React Native. It is used to display a modal dialog after the action is completed. We also dismiss the keyboard using the dismissKeyboard utility.

import dismissKeyboard from 'dismissKeyboard';
dismissKeyboard();

The error messages are displayed in a custom Error component. Error component is a functional component that accepts a message via props and displays it.

const Error = (props) => {
  return (
    <Text style={styles.error}>{props.message}</Text>
  );
}

The Error component is placed below the TextInput component and is rendered only when there is an error.

<Text style={styles.text}>Title</Text>
<TextInput
  style={styles.textInput}
  value={this.state.title}
  onChangeText={(title) => this.setState({ title })}
></TextInput>
<Error message={this.state.titleError} />

The AddPlace component should be placed within the second tab of the Places component. The onAddPlace prop is used to pass the location to the parent component.

<TabBarIOS.Item
  title="Place"
  icon={require('./assets/pin.png')}
  selected={this.state.selectedTab === 1}
  onPress={this.handleTabPress.bind(this, 1)}
>
  <AddPlace onAddPlace={this.handleAddPlace.bind(this)}  />
</TabBarIOS.Item>

The handleAddPlace method of the Places component adds the location to the annotations state. The state is immutable. The annotations array is copied over to a new array. The new array adds the location as another annotation. The state is updated with the new array.

handleAddPlace(annotation) {
  const annotations = this.state.annotations.slice();
  annotations.push(annotation);
  this.setState({ annotations });
}

The screenshot of the iOS simulator for adding a place is shown below.

after adding a place

E. Integration with Maps app

We want to integrate our app with the Map app for driving directions. To do this, we add a button to each annotation. On clicking on the button, the user is taken to the Map app for traffic navigation. We will start with making a few changes to the PlaceMap component. The render method of the component is modified as below.

render() {
  const { annotations } = this.props;
  annotations.forEach(annotation => {
    annotation.rightCalloutView = (
      <TouchableHighlight
        style={styles.button}
        onPress={this.handleNavigation.bind(this, annotation.latitude, annotation.longitude)}
      >
        <Text style={styles.buttonText}>Navigation</Text>
      </TouchableHighlight>
    );
    })
  return (
    <View style={styles.view}>
      <MapView
        style={styles.map}
        region={this.region}
        annotations={annotations}
      />
    </View>
  );
  }
}

Each annotation has a leftCalloutView, rightCalloutView, and detailCalloutView. These views display a custom component on the annotation. We will add a TouchableHighlight component to the rightCalloutView. The onPress event handler will open the Maps app for traffic navigation.

handleNavigation(la, lo) {
  const rla = this.region.latitude;
  const rlo = this.region.longitude;
  const url = `http://maps.apple.com/?saddr=${rla},${rlo}&daddr=${la},${lo}&dirflg=d`;
  return Linking.openURL(url);
}

The linking module is used to handle incoming deep links and open external deep links. Deep links allow opening another app in the mobile device with some action. The deep link for the Maps app is http://maps.apple.com. The query parameters passed to the Maps app are used to generate driving directions. The saddr parameter provides the start address which is optional. If the start address is omitted, driving directions are generated from the user's current location. The daddr parameter provides the destination address. The dirflg=d parameter generates driving directions for cars.

The screenshot for the iOS simulator after the Maps app is opened is shown below.

after Maps app is opened

F. Wrapping up

We built a geolocation travel app for the iOS platform in this tutorial. We created the initial project using react-native init command. We set tabbed navigation for our app. The first tab opens a MapView component with favorite places pinned to the map. The second tab allows the user to add more places to the map. Finally, we integrated our app to the Maps app for traffic navigation using callouts on the annotation.

The tutorial has an accompanying GitHub project.


Discover and read more posts from Vijay Thirugnanam
get started
post comments8Replies
Damik Minnegalimov
7 years ago

Thanks you!

Mohamed Sudheer
7 years ago

Please help…i am trying to export the PlaceMap component to the Places component…im not sure how the syntax works…pls reply…

Vijay Thirugnanam
7 years ago

The source code is available in the accompanying git repo. https://github.com/vijayst/react-native-places/blob/master/Places/index.ios.js.
import PlaceMap from ‘./place_map’; should import the PlaceMap component where place_map is the name of the module / file where the component is implemented.

Mohamed Sudheer
7 years ago

Okay, thanks for replying so fast…also, do you have to export Places to place_map.js so that you can set:

region = {this.region}
annotations = {this.state.annotations} 

Matt Kersner
8 years ago

You skipped a lot of steps in this tutorial, probably making a lot of assumptions about the reader’s skill level. Just wanted to point out that the entire add_place.js component part of this tutorial skips a lot (adding the error messages and ontextchange to each text input, adding the handleAddPlace method to the touchablehighlight element and binding it, etc). May want to make this a little more comprehensive. Otherwise, great work! And thanks for putting this out there!

Show more replies