Skip To Content

Get driving directions

In this topic

This topic describes how to build an iOS application to find driving directions. It's assumed that you've already added a map to your application and are able to find addresses and places as described in the Find places and addresses tutorial.

The API provides a route task (class name AGSRouteTask) that allows you to find driving directions between two or more locations. You can customize the route by specifying preferences, such as whether to find the shortest or the fastest route, or by adding constraints, such as time windows for visiting each location, barriers representing road closures, and so on.

Learn more about the route task

The route task relies on ArcGIS Server Network Analyst services. These services can be hosted in ArcGIS Online, on the Esri cloud platform, or on your on-premises servers. ArcGIS Online provides worldwide Network Analyst services you can use with a subscription to ArcGIS Online. You can also create your own services using ArcGIS for Server. In this topic, you'll use a sample routing web service on ArcGIS Online covering North America.

1. Allow a user to input locations for the route

There are a number of ways in which a user can provide locations for a route. The user can input an address, tap a location on the map, or use the device location. For the purpose of this Guide, you'll allow the user to specify the destination by geocoding an address. you'll then generate a route from the user's current location to that destination.

The Find places and addresses tutorial showed how to take an address or place name and display its location on the map. In this tutorial, you'll build on that and allow the user to route to that location by first tapping on the graphic and then tapping the accessory button in the callout. To do so, you need to set the view controller as the callout's delegate when the view loads. This will allow the callout to inform you by invoking the didClickAccessoryButtonForCallout: method when a user taps the accessory button.

override func viewDidLoad() {

 super.viewDidLoad()

 //Add a basemap tiled layer
 let url = NSURL(string: "http://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer")
 let tiledLayer = AGSTiledMapServiceLayer(URL: url)
 self.mapView.addMapLayer(tiledLayer, withName: "Basemap Tiled Layer")

 //Set the map view's layer delegate
 self.mapView.layerDelegate = self
 
//Set the callout delegate
 self.mapView.callout.delegate = self
}

func mapViewDidLoad(mapView: AGSMapView!) {
 //do something now that the map is loaded
 //for example, show the current location on the map
 mapView.locationDisplay.startDataSource()
}

When the user taps the accessory button, you'll retrieve the graphic for which the callout was being shown. You'll then obtain the graphic's geometry and use it as the destination for the route.

func didClickAccessoryButtonForCallout(callout: AGSCallout!) {
 let graphic = callout.representedObject as AGSGraphic
 let destinationLocation = graphic.geometry

 self.routeTo(destinationLocation)

}

2. Calculate the route

In this section, you'll implement the routeToDestination: method and calculate a route from the device's current location to the destination.

To calculate a route, you first need to instantiate the route task. To do this, you need the URL of a routing service, and optionally, the user credentials to access the service. Since the service you'll be using in this topic is public, you don't need credentials to access it.

You also need to set the task's delegate. This is because the task performs its operations, such as invoking the route service, on a background thread. When the operation is complete, the task needs a way to inform you. It does so by invoking methods on the delegate. In this example, you'll set the view controller as the task's delegate by setting the task's delegate property to self and by making the view controller adopt the AGSRouteTaskDelegate protocol. As part of adopting the protocol, you need to add the declaration to the view controller's swift file, and implement relevant methods defined in the protocol .

func routeTo(destination:AGSGeometry) {

 if self.routeTask == nil {
  self.routeTask = AGSRouteTask(URL: NSURL(string: "http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Network/USA/NAServer/Route"))
  self.routeTask.delegate = self
 }

 ...

Now that you have a route task, you need to specify the route to calculate. To do this, instantiate an object of the AGSRouteTaskParameters class and set some properties on it. To specify the locations along the route, invoke setStopsWithFeatures: using the device location as the start point and the destination location as the end point. Set some additional route preferences on the parameters object, and invoke solveWithParameters: on the route task to calculate the route.

...

 let params = AGSRouteTaskParameters()
 let firstStop = AGSStopGraphic(geometry: self.mapView.locationDisplay.mapLocation(), symbol: nil, attributes: nil)
 let lastStop = AGSStopGraphic(geometry: destination, symbol: nil, attributes: nil)
 params.setStopsWithFeatures([firstStop, lastStop])

 //This returns entire route
 params.returnRouteGraphics = true

 //This returns turn by turn directions
 params.returnDirections = true

 //We don't want our stops reordered
 params.findBestSequence = false 
 params.preserveFirstStop = true
 params.preserveLastStop = true

 //ensure the graphics are returned in our maps spatial reference
 params.outSpatialReference = self.mapView.spatialReference

 //Don't ignore invalid stops, raise error instead
 params.ignoreInvalidLocations = false
 self.routeTask.solveWithParameters(params)

}

3. Display the route and driving directions

In this section, you'll display the route returned by the route task on the map. To do this, you'll add the route as a graphic to the graphics layer. You'll also display turn-by-turn driving directions and highlight the portion of the route that corresponds to each direction.

Before you start with the code, you need to add a few UI elements to show turn-by-turn directions and allow the user to step through those directions. You'll add a toolbar to the bottom of the view containing two buttons (Previous and Next). You'll also add a label on top of the toolbar between the buttons. The label will show the active maneuver and the buttons will allow the user to iterate through each maneuver in the driving directions.

Note:

When adding UI components, you typically also need to specify Auto Layout constraints for the components so that they can be properly sized and positioned when the layout of your application changes, for example, when the device is rotated. For the sake of simplicity, we will skip setting layout constraints in this tutorial, but you can look at the accompanying tutorial app to see these constraints in action.

Next, you need to connect the label to an outlet on the view controller called directionsLabel, the left button to an outlet called prevBtn, and the right button to an outlet called nextBtn. This will allow you to programmatically access these UI elements from code.

You'll also connect the Selector of both the buttons to actions on the view controller called prevBtnClicked and nextBtnClicked, respectively.

When the task completes its operation, it will inform the view controller by invoking the routeTask:operation:didSolveWithResult: method defined in the AGSRouteTaskDelegate protocol. In this method, you'll first check whether the results array is empty, and if so, inform the user that no results were found. If results were found, you'll take the first route, add it to the graphics layer, and zoom in on the map closer to the route. You'll also enable the buttons on the UI to allow the user to step through the driving directions.

func routeTask(routeTask: AGSRouteTask!, operation op: NSOperation!, didSolveWithResult routeTaskResult: AGSRouteTaskResult!) {

 //update our banner with status
 self.directionsLabel.text = "Route computed"
 
 //Remove existing route from map (if it exists)
 if self.routeResult != nil {
  self.graphicLayer.removeGraphic(self.routeResult.routeGraphic)
 }

 //Check if you got any results back
 if routeTaskResult.routeResults != nil {
  
  //you know that you are only dealing with 1 route...
  self.routeResult = routeTaskResult.routeResults[0] as? AGSRouteResult
  if self.routeResult != nil && self.routeResult.routeGraphic != nil {
   
   //symbolize the returned route graphic
   let yellowLine = AGSSimpleLineSymbol(color: UIColor.orangeColor(), width: 8.0)
   self.routeResult.routeGraphic.symbol = yellowLine
 
   //add the graphic to the graphics layer
   self.graphicLayer.addGraphic(self.routeResult.routeGraphic)

   //enable the next button so the suer can traverse directions
   self.nextBtn.enabled = true
   self.prevBtn.enabled = false
   self.currentDirectionGraphic = nil
   self.mapView.zoomToGeometry(self.routeResult.routeGraphic.geometry, withPadding: 100, animated: true)

   return
  }
 }
 
 //show alert if you didn't get results
 UIAlertView(title: "No Route", message: "No Routes Found", delegate: nil, cancelButtonTitle: "OK").show()

}

As the user taps the Previous or Next buttons to iterate through the driving directions, you'll display each direction's maneuver in the label and highlight the corresponding section of the route.

@IBAction func nextBtnClicked(sender: AnyObject) {
 var index = 0
 
 if self.currentDirectionGraphic != nil {
  if let currentIndex = find(self.routeResult.directions.graphics as [AGSDirectionGraphic], self.currentDirectionGraphic) {
   index = currentIndex + 1
  }
 }

 self.displayDirectionForIndex(index)
}


@IBAction func prevBtnClicked(sender: AnyObject) {
 var index = 0

 if self.currentDirectionGraphic != nil {
  if let currentIndex = find(self.routeResult.directions.graphics as [AGSDirectionGraphic], self.currentDirectionGraphic) {
   index = currentIndex - 1
  }
 }

 self.displayDirectionForIndex(index)
}


func displayDirectionForIndex(index:Int) {
 //remove current direction graphic, so you can display next one
 self.graphicLayer.removeGraphic(self.currentDirectionGraphic)

 //get current direction and add it to the graphics layer
 let directions = self.routeResult.directions as AGSDirectionSet
 self.currentDirectionGraphic = directions.graphics[index] as AGSDirectionGraphic

 //highlight current manoeuver with a different symbol
 let cs = AGSCompositeSymbol()
 let sls1 = AGSSimpleLineSymbol()
 sls1.color = UIColor.whiteColor()
 sls1.style = .Solid
 sls1.width = 8
 cs.addSymbol(sls1)
 let sls2 = AGSSimpleLineSymbol()
 sls2.color = UIColor.redColor()
 sls2.style = .Dash
 sls2.width = 4
 cs.addSymbol(sls2)
 self.currentDirectionGraphic.symbol = cs
 self.graphicLayer.addGraphic(self.currentDirectionGraphic)

 //update banner
 self.directionsLabel.text = self.currentDirectionGraphic.text
 
 //soom to envelope of the current direction (expanded by a factor of 1.3)
 let env = self.currentDirectionGraphic.geometry.envelope.mutableCopy() as AGSMutableEnvelope
 env.expandByFactor(1.3)
 self.mapView.zoomToEnvelope(env, animated: true)

 //determine if you need to disable the next/prev button
 if index >= self.routeResult.directions.graphics.count - 1 {
  self.nextBtn.enabled = false
 }
 else {
  self.nextBtn.enabled = true
 }

 if index > 0 {
  self.prevBtn.enabled = true
 }
 else {
  self.prevBtn.enabled = false
 }
}

4. Run the application

At this point, you've written all the code required to allow a user to tap a feature on a map and route to it from the user's current location. The final thing to do is declare the properties you've been using as well as the delegate protocols you've adopted. You do this in the view controller's swift file.

class ViewController: UIViewController, AGSMapViewLayerDelegate, UISearchBarDelegate, AGSLocatorDelegate, AGSCalloutDelegate, AGSRouteTaskDelegate {

 @IBOutlet weak var mapView: AGSMapView!
 @IBOutlet weak var nextBtn: UIBarButtonItem!
 @IBOutlet weak var prevBtn: UIBarButtonItem!
 @IBOutlet weak var directionsLabel: UILabel!
 
 var graphicLayer:AGSGraphicsLayer!
 var locator:AGSLocator!
 var calloutTemplate:AGSCalloutTemplate!
 var routeTask:AGSRouteTask!
 var routeResult:AGSRouteResult!
 var currentDirectionGraphic:AGSDirectionGraphic!