
Getting Directions in MapKit with SwiftUI
Learn how to get directions and show a route on a Map view in SwiftUI.
A typical scenario could be to calculate travel time and directions for any of the selected search results. Based on a selectedResult
we can calculate a route of type MKRoute
within a getDirections()
function.
import SwiftUI
import MapKit
struct ContentView: View {
@State private var selectedResult: MKMapItem?
@State private var route: MKRoute?
var body: some View {
Map(selection: $selectedResult) {
// ...
}
}
func getDirections() {
}
}
Initial set-up of your view.
Configure a MKDirections.Request()
and with source location and destination location. In this example, we are using a fixed location as the source location, but of course, this could be the user's current location or any other coordinate.
func getDirections() {
self.route = nil
// Check if there is a selected result
guard let selectedResult else { return }
// Coordinate to use as a starting point for the example
let startingPoint = CLLocationCoordinate2D(
latitude: 40.83657722488077,
longitude: 14.306896671048852
)
// Create and configure the request
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: startingPoint))
request.destination = self.selectedResult
// Get the directions based on the request
Task {
let directions = MKDirections(request: request)
let response = try? await directions.calculate()
route = response?.routes.first
}
}
The request is run asynchronously. Once you get a response you can access the valid route objects and then add them to a property in your view, so you can use them in your interface. Here we have a property called route
that will store the first valid route from our response object.
Let’s use the .onChange
modifier to monitor the selectedResult
property and whenever it changes, we call the getDirections()
function.
struct ContentView: View {
...
var body: some View {
Map(selection: $selectedResult) {
// ...
}
.onChange(of: selectedResult){
getDirections()
}
}
...
}
Getting the direction once a location is selected.
Lastly, in our Map
view, we can add a MapPolyLine
object if a route is available. We will use a blue stroke to display the way to the selected point of interest.
struct ContentView: View {
@State private var selectedResult: MKMapItem?
@State private var route: MKRoute?
private let startingPoint = CLLocationCoordinate2D(
latitude: 40.83657722488077,
longitude: 14.306896671048852
)
var body: some View {
Map(selection: $selectedResult) {
// Adding the marker for the starting point
Marker("Start", coordinate: self.startingPoint)
// Show the route if it is available
if let route {
MapPolyline(route)
.stroke(.blue, lineWidth: 5)
}
}
.onChange(of: selectedResult){
getDirections()
}
}
...
}
Adding the polyline on the map to represent the route
You can also get other types of information from a route, like the travel time between the source and destination locations. Let’s create a computed property called travelTime
. It uses the route
object to read and return its expected travel time through the expectedTravelTime
property. To get it in the format of hours and minutes use a formatter object.
struct ContentView: View {
@State private var selectedResult: MKMapItem?
@State private var route: MKRoute?
private var travelTime: String? {
// Check if there is a route to get the info from
guard let route else { return nil }
// Set up a date formater
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .abbreviated
formatter.allowedUnits = [.hour, .minute]
// Format the travel time to the format you want to display it
return formatter.string(from: route.expectedTravelTime)
}
var body: some View { ... }
func getDirections() { ... }
}
Creating the computed property to read the travel time of the route.
Here is a completely functional example showing the route between two hard-coded points on a Map view:
import SwiftUI
import MapKit
struct ContentView: View {
@State private var selectedResult: MKMapItem?
@State private var route: MKRoute?
private let startingPoint = CLLocationCoordinate2D(
latitude: 40.83657722488077,
longitude: 14.306896671048852
)
private let destinationCoordinates = CLLocationCoordinate2D(
latitude: 40.849761,
longitude: 14.263364
)
var body: some View {
Map(selection: $selectedResult) {
// Adding the marker for the starting point
Marker("Start", coordinate: self.startingPoint)
// Show the route if it is available
if let route {
MapPolyline(route)
.stroke(.blue, lineWidth: 5)
}
}
.onChange(of: selectedResult){
getDirections()
}
.onAppear {
self.selectedResult = MKMapItem(placemark: MKPlacemark(coordinate: self.destinationCoordinates))
}
}
func getDirections() {
self.route = nil
// Check if there is a selected result
guard let selectedResult else { return }
// Create and configure the request
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: self.startingPoint))
request.destination = self.selectedResult
// Get the directions based on the request
Task {
let directions = MKDirections(request: request)
let response = try? await directions.calculate()
route = response?.routes.first
}
}
}
Fully functioning example. Copy and paste in an app project to see it working.