In this fifth and final episode of our series on Maps in SwiftUI, we’ll learn how to:
- Draw the route
- Calculate the travel time
- Change the transport type
As usual, let’s start from the beginning.
In the code, we set the starting point at Infinite Loop and the ending point at Apple Park. By clicking on the car icon, we change the transport type to walking.
Now, let’s begin analyzing the declarations in the view:
struct RouteView: View { @State private var route: MKRoute? @State private var formattedTravelTime: String = "" @State private var transportType: MKDirectionsTransportType = .automobile private let infiniteLoop = CLLocationCoordinate2D(latitude: 37.33218745278164, longitude: -122.03010316931332) private let applePark = CLLocationCoordinate2D(latitude: 37.33536036460403, longitude: -122.00901491534331) private let images = [MKDirectionsTransportType.automobile.rawValue: "car", MKDirectionsTransportType.walking.rawValue: "figure.walk"] . . .
The route will contain the polyline of the route, while formattedTravelTime holds the time needed to complete the journey. transportType contains the mode of transportation used for the journey (in this example, we use only automobile and walking). infiniteLoop and applePark are the two points of interest that we want to connect. The images variable contains a dictionary with two elements, each representing the image name used for the transport type.
var body: some View { Map { if let route { MapPolyline(route.polyline) .stroke(.blue, lineWidth: 8) } } .overlay(alignment: .bottom, content: { HStack { Text("Travel time: \(formattedTravelTime)") .font(.headline) .foregroundStyle(.black) .fontWeight(.bold) Button(action: { if transportType == .automobile { transportType = .walking } else { transportType = .automobile } fetchRouteFrom(infiniteLoop, to: applePark) }, label: { Image(systemName: images[transportType.rawValue] ?? "questionmark.app") }) }.padding() .background(.ultraThinMaterial) .cornerRadius(15) }) .onAppear(perform: { fetchRouteFrom(infiniteLoop, to: applePark) }) }
With this code, we display a map and, if not nil, the route as a polyline. The route is calculated by the fetchRouteFrom function. This function is called when the view is displayed and every time we tap on the transport type button. Next to the button, the required travel time is also displayed.
Now, let’s take a look at the functions used:
private func fetchRouteFrom(_ source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) { let request = MKDirections.Request() request.source = MKMapItem(placemark: MKPlacemark(coordinate: source)) request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destination)) request.transportType = transportType Task { let result = try? await MKDirections(request: request).calculate() route = result?.routes.first if let time = travelTime() { formattedTravelTime = time } else { formattedTravelTime = "unknown" } } }
These functions receive the coordinates of the two points as parameters. A request to MKDirections is created, setting the placemarks for the source and destination, as well as the transport type.
Following this, we calculate the route, retrieve the first available route, and then calculate the travel time with:
private func travelTime() -> String? { guard let route else { return nil} let formatter = DateComponentsFormatter() formatter.unitsStyle = .abbreviated formatter.allowedUnits = [.hour, .minute] return formatter.string(from: route.expectedTravelTime) }
In this function, we obtain the travel time using route.expectedTravelTime and format it with a formatter.
Now, let’s put everything together:
import SwiftUI import MapKit struct ContentView: View { @State private var route: MKRoute? @State private var formattedTravelTime: String = "" @State private var transportType: MKDirectionsTransportType = .automobile private let infiniteLoop = CLLocationCoordinate2D(latitude: 37.33218745278164, longitude: -122.03010316931332) private let applePark = CLLocationCoordinate2D(latitude: 37.33536036460403, longitude: -122.00901491534331) private let images = [MKDirectionsTransportType.automobile.rawValue: "car", MKDirectionsTransportType.walking.rawValue: "figure.walk"] var body: some View { Map { if let route { MapPolyline(route.polyline) .stroke(.blue, lineWidth: 8) } } .overlay(alignment: .bottom, content: { HStack { Text("Travel time: \(formattedTravelTime)") .font(.headline) .foregroundStyle(.black) .fontWeight(.bold) Button(action: { if transportType == .automobile { transportType = .walking } else { transportType = .automobile } fetchRouteFrom(infiniteLoop, to: applePark) }, label: { Image(systemName: images[transportType.rawValue] ?? "questionmark.app") }) }.padding() .background(.ultraThinMaterial) .cornerRadius(15) }) .onAppear(perform: { fetchRouteFrom(infiniteLoop, to: applePark) }) } private func fetchRouteFrom(_ source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) { let request = MKDirections.Request() request.source = MKMapItem(placemark: MKPlacemark(coordinate: source)) request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destination)) request.transportType = transportType Task { let result = try? await MKDirections(request: request).calculate() route = result?.routes.first if let time = travelTime() { formattedTravelTime = time } else { formattedTravelTime = "unknown" } } } private func travelTime() -> String? { guard let route else { return nil} let formatter = DateComponentsFormatter() formatter.unitsStyle = .abbreviated formatter.allowedUnits = [.hour, .minute] return formatter.string(from: route.expectedTravelTime) } }
With this episode, our series on maps is now complete.
Stay tuned and follow me for upcoming topics.