Learn how to find a route and directions with the route service.
Routing is the process of finding the path from an origin to a destination in a street network. You can use the Routing service to find routes, get driving directions, calculate drive times, and solve complicated, multiple vehicle routing problems. To create a route, you typically define a set of stops (origin and one or more destinations) and use the service to find a route with directions. You can also use a number of additional parameters such as barriers and mode of travel to refine the results.
In this tutorial, you define an origin and destination by clicking on the map. These values are used to get a route and directions from the route service. The directions are also displayed on the map.
Prerequisites
Before starting this tutorial:
-
You need an ArcGIS Location Platform or ArcGIS Online account.
-
Your system meets the system requirements.
Steps
Get an access token
You need an access token to use the location services used in this tutorial.
-
Go to the Create an API key tutorial to obtain an access token using your ArcGIS Location Platform or ArcGIS Online account.
-
Ensure that the following privileges are enabled: Location services > Basemaps > Basemap styles service and Location services > Routing.
-
Copy the access token as it will be used in the next step.
To learn more about other ways to get an access token, go to Types of authentication.
Open the Xcode project
-
To start the tutorial, complete the Display a map tutorial or download and unzip the solution.
-
Open the
.xcodeproj
file in Xcode. -
In Xcode, in the Project Navigator, click AppDelegate.swift.
-
In the editor, set the
API
property on theKey AGS
with your access token.ArcGIS Runtime Environment AppDelegate.swiftUse dark colors for code blocks func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { AGSArcGISRuntimeEnvironment.apiKey = "YOUR_ACCESS_TOKEN" return true }
Update the map
A navigation basemap layer is typically used in routing applications. Update the basemap to use the .arc
basemap style, and change the position of the map to center on Los Angeles.
-
Update the
AGS
style property fromBasemap .arc
toGIS Topographic .arc
and update the latitude and longitude coordinates to center on Los Angeles.GIS Navigation ViewController.swiftUse dark colors for code blocks 27 28 37Change line Change line Change line Change line Change line Change line Change line Change line private func setupMap() { mapView.map = AGSMap(basemapStyle: .arcGISNavigation) mapView.setViewpoint( AGSViewpoint( latitude: 34.05293, longitude: -118.24368, scale: 288_895 ) ) }
Receive map view touch events
The app will use locations derived from a user tapping the map view to generate the stops of a route. Conform the view controller to receive touch events from the map view. The locations derived from a user tapping the map view will be used to generate routes in a later step.
-
In Xcode, in the Project Navigator, click ViewController.swift.
-
In the editor, extend
View
to conform to theController AGS
protocol and include theGeo View Touch Delegate geo
geoview touch delegate method.View :did Tap At Screen Point :map Point :() ViewController.swiftUse dark colors for code blocks 41 42Add line. Add line. Add line. Add line. Add line. Add line. Add line. // MARK: - GeoView Touch Delegate extension ViewController: AGSGeoViewTouchDelegate { func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { } }
-
In the
setup
method, assignMap() View
toController map
.View.delegate
This step bridges map
user touch interactions with View
via the AGS
protocol.
private func setupMap() {
mapView.touchDelegate = self
mapView.map = AGSMap(basemapStyle: .arcGISNavigation)
mapView.setViewpoint(
AGSViewpoint(
latitude: 34.05293,
longitude: -118.24368,
scale: 288_895
)
)
}
Add graphics to the map view
A graphics overlay is a container for graphics. Graphics are added as a visual means to display the search result on the map.
-
Create a private
AGS
property namedGraphic start
. This graphic will be used to display the route's start location.Graphic An
AGS
is used to display a location on the map view.Simple Marker Symbol ViewController.swiftUse dark colors for code blocks 41 42 49Add line. Add line. Add line. Add line. Add line. Add line. // MARK: - Route Graphics private let startGraphic: AGSGraphic = { let symbol = AGSSimpleMarkerSymbol(style: .circle, color: .white, size: 8) symbol.outline = AGSSimpleLineSymbol(style: .solid, color: .black, width: 1) let graphic = AGSGraphic(geometry: nil, symbol: symbol) return graphic }()
-
Create a private
AGS
property namedGraphic end
. This graphic will be used to display the route's end location.Graphic An
AGS
is used to display a location on the map view.Simple Marker Symbol ViewController.swiftUse dark colors for code blocks 41 42 43 44 45 46 47 48 49 56Add line. Add line. Add line. Add line. Add line. Add line. // MARK: - Route Graphics private let startGraphic: AGSGraphic = { let symbol = AGSSimpleMarkerSymbol(style: .circle, color: .white, size: 8) symbol.outline = AGSSimpleLineSymbol(style: .solid, color: .black, width: 1) let graphic = AGSGraphic(geometry: nil, symbol: symbol) return graphic }() private let endGraphic: AGSGraphic = { let symbol = AGSSimpleMarkerSymbol(style: .circle, color: .black, size: 8) symbol.outline = AGSSimpleLineSymbol(style: .solid, color: .black, width: 1) let graphic = AGSGraphic(geometry: nil, symbol: symbol) return graphic }()
-
Create a private
AGS
property namedGraphic route
. This graphic will be used to display the route line.Graphic An
AGS
is used to display a line on the map view.Simple Line Symbol ViewController.swiftUse dark colors for code blocks 50 51 52 53 54 55 56Add line. Add line. Add line. Add line. Add line. private let endGraphic: AGSGraphic = { let symbol = AGSSimpleMarkerSymbol(style: .circle, color: .black, size: 8) symbol.outline = AGSSimpleLineSymbol(style: .solid, color: .black, width: 1) let graphic = AGSGraphic(geometry: nil, symbol: symbol) return graphic }() private let routeGraphic: AGSGraphic = { let symbol = AGSSimpleLineSymbol(style: .solid, color: .blue, width: 3) let graphic = AGSGraphic(geometry: nil, symbol: symbol) return graphic }()
-
Define a private method named
add
. InGraphics() add
create anGraphics() AGS
. AppendGraphics Overlay start
,Graphic end
, andGraphic route
to the graphics overlay and add the graphics overlay to the map view.Graphic Because
start
,Graphic end
, andGraphic route
haven't yet specified a geometry, they will not be visible.Graphic ViewController.swiftUse dark colors for code blocks 57 58 59 60 61 62Add line. Add line. Add line. Add line. Add line. private let routeGraphic: AGSGraphic = { let symbol = AGSSimpleLineSymbol(style: .solid, color: .blue, width: 3) let graphic = AGSGraphic(geometry: nil, symbol: symbol) return graphic }() private func addGraphics() { let routeGraphics = AGSGraphicsOverlay() mapView.graphicsOverlays.add(routeGraphics) routeGraphics.graphics.addObjects(from: [routeGraphic, startGraphic, endGraphic]) }
-
In the
view
method, callDid Load() add
.Graphics() ViewController.swiftUse dark colors for code blocks 21 22 23 24 26 27Add line. override func viewDidLoad() { super.viewDidLoad() setupMap() addGraphics() }
Define app route builder status
The app will leverage a state-machine design pattern to ensure the contents of the map reflect the state of gathering the route parameters and generating the route result. This design pattern supports wrangling multiple asynchronous requests to the world routing service into a single state variable result, ensuring the reliability of route results.
-
Define an enum named
Route
with four cases. These four cases are used to gather route parameters and display the route's result, over time.Builder Status ViewController.swiftUse dark colors for code blocks 71 72 80Add line. Add line. Add line. Add line. Add line. Add line. Add line. // MARK: - Route Builder private enum RouteBuilderStatus { case none case selectedStart(AGSPoint) case selectedStartAndEnd(AGSPoint, AGSPoint) case routeSolved(AGSPoint, AGSPoint, AGSRoute) }
-
Define a method of
Route
namedBuilder Status next
. This method is used to step through generating a route, over time.Status With :point() ViewController.swiftUse dark colors for code blocks 71 72 73 74 75 76 77 78 91 92 93Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. // MARK: - Route Builder private enum RouteBuilderStatus { case none case selectedStart(AGSPoint) case selectedStartAndEnd(AGSPoint, AGSPoint) case routeSolved(AGSPoint, AGSPoint, AGSRoute) func nextStatus(with point: AGSPoint) -> RouteBuilderStatus { switch self { case .none: return .selectedStart(point) case .selectedStart(let start): return .selectedStartAndEnd(start, point) case .selectedStartAndEnd: return .selectedStart(point) case .routeSolved: return .selectedStart(point) } } }
-
Create a private
Route
property namedBuilder Status status
. Set the default value.none
.This property maintains the status of route parameters as they are gathered and the route is generated, over time. Updating the status will update the geometry of route graphics and display them in the map view.
ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. private var status: RouteBuilderStatus = .none { didSet { switch status { case .none: startGraphic.geometry = nil endGraphic.geometry = nil routeGraphic.geometry = nil case .selectedStart(let start): startGraphic.geometry = start endGraphic.geometry = nil routeGraphic.geometry = nil case .selectedStartAndEnd(let start, let end): startGraphic.geometry = start endGraphic.geometry = end routeGraphic.geometry = nil case .routeSolved(let start, let end, let route): startGraphic.geometry = start endGraphic.geometry = end routeGraphic.geometry = route.routeGeometry } } }
Add a UI to display driving directions
To display the turn-by-turn directions from the route, a UI element is required.
-
Create a private lazy
UI
property namedBar Button Item directions
. Lazy loading the bar button item guaranteesButton self
is created prior to assigning the button's target.ViewController.swiftUse dark colors for code blocks 117 118 124Add line. Add line. Add line. Add line. Add line. // MARK: - Directions Button private lazy var directionsButton: UIBarButtonItem = { let button = UIBarButtonItem(title: "Show Directions", style: .plain, target: self, action: #selector(displayDirections)) button.isEnabled = false return button }()
-
Define a method named
display
and assign it theDirections :() @objc
keyword. The@objc
method keyword exposes the method to Objective-C, a necessary step for using theUI
API.Bar Button Item This method collates the route maneuvers and presents the directions to the user in an alert.
ViewController.swiftUse dark colors for code blocks 117 118 119 120 121 122 123 124Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. // MARK: - Directions Button private lazy var directionsButton: UIBarButtonItem = { let button = UIBarButtonItem(title: "Show Directions", style: .plain, target: self, action: #selector(displayDirections)) button.isEnabled = false return button }() @objc func displayDirections(_ sender: AnyObject) { guard case let .routeSolved(_, _, route) = status else { return } let directions = route.directionManeuvers.enumerated() .reduce(into: "\n") { $0 += "\($1.offset + 1). \($1.element.directionText).\n\n" } let alert = UIAlertController(title: "Directions", message: directions, preferredStyle: .alert) let okay = UIAlertAction(title: "Hide Directions", style: .default, handler: nil) alert.addAction(okay) present(alert, animated: true, completion: nil) }
-
In the
status
property'sdid
closure, update each state to enable or disable theSet directions
. The directions button should be enabled only if the route is solved.Button ViewController.swiftUse dark colors for code blocks 94 95 96 97 98 99 100 102 103 104 105 107 108 109 110 112 113 114 115 117 118 119Add line. Add line. Add line. Add line. private var status: RouteBuilderStatus = .none { didSet { switch status { case .none: startGraphic.geometry = nil endGraphic.geometry = nil routeGraphic.geometry = nil directionsButton.isEnabled = false case .selectedStart(let start): startGraphic.geometry = start endGraphic.geometry = nil routeGraphic.geometry = nil directionsButton.isEnabled = false case .selectedStartAndEnd(let start, let end): startGraphic.geometry = start endGraphic.geometry = end routeGraphic.geometry = nil directionsButton.isEnabled = false case .routeSolved(let start, let end, let route): startGraphic.geometry = start endGraphic.geometry = end routeGraphic.geometry = route.routeGeometry directionsButton.isEnabled = true } } }
-
In
view
, set theDid Load() navigation
toItem.right Bar Button Item directions
.Button ViewController.swiftUse dark colors for code blocks 21 22 23 24 25 26 28 29Add line. override func viewDidLoad() { super.viewDidLoad() setupMap() addGraphics() navigationItem.rightBarButtonItem = directionsButton }
-
In Xcode, in the Project Navigator, click Main.storyboard.
-
In the editor, select
View
. In the menu bar, click Editor > Embed In > Navigation Controller.Controller Embedding
View
within a Navigation Controller will place a navigation bar at the top ofController View
. Inside the navigation bar you will find the directions bar button item.Controller
Create a route task and route parameters
A task makes a request to a service and returns the results. Use the AGS
class to access a routing service. A routing service with global coverage is part of ArcGIS location services. You can also publish custom routing services using ArcGIS Enterprise.
-
In Xcode, in the Project Navigator, click ViewController.swift.
-
In the editor, create a private
AGS
property namedRoute Task route
with the routing service.Task ViewController.swiftUse dark colors for code blocks 142 143 148Add line. Add line. Add line. Add line. // MARK: - Route Task private let routeTask: AGSRouteTask = { let worldRoutingService = URL(string: "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World")! return AGSRouteTask(url: worldRoutingService) }()
-
Create a private optional
AGS
property namedCancelable current
.Solve Route Operation This property will maintain a reference to the route task operation in case a user submits a second query and the operation should be canceled.
ViewController.swiftUse dark colors for code blocks 144 145 146 147 148Add line. private let routeTask: AGSRouteTask = { let worldRoutingService = URL(string: "https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World")! return AGSRouteTask(url: worldRoutingService) }() private var currentSolveRouteOperation: AGSCancelable?
-
Define a private method named
solve
and cancel the current route task operation, if one exists.Route Start :end :completion :() ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. private func solveRoute(start: AGSPoint, end: AGSPoint, completion: @escaping (Result<[AGSRoute], Error>) -> Void) { currentSolveRouteOperation?.cancel() }
-
Get default
AGS
from theRoute Parameters route
.Task The
default
method is asynchronous and you must handle its completion and check for errors.Route Parameters With Completion :() ViewController.swiftUse dark colors for code blocks 151 152 153 154 163 164Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. private func solveRoute(start: AGSPoint, end: AGSPoint, completion: @escaping (Result<[AGSRoute], Error>) -> Void) { currentSolveRouteOperation?.cancel() currentSolveRouteOperation = routeTask.defaultRouteParameters { [weak self] (defaultParameters, error) in guard let self = self else { return } if let error = error { completion(.failure(error)) return } } }
-
Modify the default parameters with start and end route stops provided by the user.
ViewController.swiftUse dark colors for code blocks 151 152 153 154 155 156 157 158 159 160 161 165 166 167 168Add line. Add line. Add line. private func solveRoute(start: AGSPoint, end: AGSPoint, completion: @escaping (Result<[AGSRoute], Error>) -> Void) { currentSolveRouteOperation?.cancel() currentSolveRouteOperation = routeTask.defaultRouteParameters { [weak self] (defaultParameters, error) in guard let self = self else { return } if let error = error { completion(.failure(error)) return } guard let params = defaultParameters else { return } params.returnDirections = true params.setStops([AGSStop(point: start), AGSStop(point: end)]) } }
-
Perform the
solve
method and supply the parameters containing the route stops.Route With Parameters :completion :() ViewController.swiftUse dark colors for code blocks 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 173 174 175 176Add line. Add line. Add line. Add line. Add line. Add line. Add line. private func solveRoute(start: AGSPoint, end: AGSPoint, completion: @escaping (Result<[AGSRoute], Error>) -> Void) { currentSolveRouteOperation?.cancel() currentSolveRouteOperation = routeTask.defaultRouteParameters { [weak self] (defaultParameters, error) in guard let self = self else { return } if let error = error { completion(.failure(error)) return } guard let params = defaultParameters else { return } params.returnDirections = true params.setStops([AGSStop(point: start), AGSStop(point: end)]) self.currentSolveRouteOperation = self.routeTask.solveRoute(with: params) { (routeResult, error) in if let routes = routeResult?.routes { completion(.success(routes)) } else if let error = error { completion(.failure(error)) } } } }
Generate a route from user tap gestures
A user's tap gestures are used to generate a route. The geo view touch delegate forwards messages from the map view to View
, allowing the app to decide what step to take next.
-
Find the
View
extension ofController AGS
and theGeo View Touch Delegate geo
method.View :did Tap At Screen Point :map Point :() -
Cancel the current solve route operation, if one exists.
ViewController.swiftUse dark colors for code blocks 182 183 184 185 188 189 190Add line. Add line. extension ViewController: AGSGeoViewTouchDelegate { func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { currentSolveRouteOperation?.cancel() } }
-
Set the next status using the
Route
's helper method,Builder Status next
.Status With :point() ViewController.swiftUse dark colors for code blocks 184 185 186 187 189 190Add line. func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { currentSolveRouteOperation?.cancel() status = status.nextStatus(with: mapPoint) }
-
If start and end locations are gathered, perform
solve
. The result is used to updateRoute :start :end() status
and draw the route's line on the map.ViewController.swiftUse dark colors for code blocks 184 185 186 187 188 189 206 207Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { currentSolveRouteOperation?.cancel() status = status.nextStatus(with: mapPoint) if case let .selectedStartAndEnd(start, end) = status { solveRoute(start: start, end: end) { [weak self] (result) in guard let self = self else { return } switch result { case .failure(let error): print(error.localizedDescription) self.status = .none case .success(let routes): if let route = routes.first { self.status = .routeSolved(start, end, route) } else { self.status = .none } } } } }
-
Press Command + R to run the app.
If you are using the Xcode simulator your system must meet these minimum requirements: macOS Big Sur 11.3, Xcode 13, iOS 13. If you are using a physical device, then refer to the system requirements.
The map should support two taps to create an origin and destination point and then use the route service to display the resulting route.
What's next?
Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials: