Routing With MapKit and Core Location

Learn how to use MapKit and CoreLocation to help users with address completion and route visualization using multiple addresses. By Ryan Ackermann.

Leave a rating/review
Download materials
Save for later
Share
Update note: Ryan Ackermann updated this tutorial for iOS 13, Xcode 11 and Swift 5. Lyndsey Scott wrote the original.

Apple’s been hard at work improving its maps with better land detail, pedestrian data and road coverage, closing the gap between it and its competitors. So hop aboard the Apple Maps bandwagon by getting to know MapKit and CoreLocation.

In this tutorial, you’ll create an app named RWRouter to help you find a round-trip route between your starting point and up to two other locations. To do that, you’ll use CoreLocation and MKLocalSearch to fetch address data and MKDirections to find the quickest route between addresses.

In this tutorial, you’ll learn how to:

  • Handle user location and authorization requests with CoreLocation.
  • Reverse geocode with CLGeocoder to convert a location’s coordinates into a human-readable address.
  • Use MKLocalSearchCompleter to autocomplete an address.
  • Generate routes using MKRoute and display them using MapKit.

Now, you’re ready to dive into your map app!

Getting Started

To get started, click the Download Materials button at the top or bottom of this tutorial. Inside the zip file, you’ll find two folders: final and starter. Open the starter folder.

The first view of the app has three text fields: one for the starting/ending address and two for the in-between stops. The second view has a map view and a table view to show the routes and directions.

The app’s layout is complete, but it’s up to you to add the features.

Using MapKit With CoreLocation

What’s the difference between CoreLocation and MapKit?

  • CoreLocation handles your location data. It uses all available components on the device including the Wi-Fi, GPS, Bluetooth, magnetometer, barometer and cellular hardware.
  • MapKit is all about visual operations, like rendering maps, and user-friendly operations, like address search and route directions. After all, who wants to type latitude and longitude instead of a human-readable address?

In the next steps, you’ll gather the user’s starting location with CoreLocation, use MapKit to handle addresses that the user enters manually, create a round-trip route between them and then, finally, use all that data to show a map with the entire route. Cool!

Getting the User’s Location With CoreLocation

In ViewControllers/RouteSelectionViewController.swift, add the following property to the top of the class:

private let locationManager = CLLocationManager()

You declare locationManager as a property of the class so you can access it as needed.

Next, add the following code to attemptLocationAccess() to set up and instantiate CLLocationManager:

// 1
guard CLLocationManager.locationServicesEnabled() else {
  return
}
// 2
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
// 3
locationManager.delegate = self
// 4
if CLLocationManager.authorizationStatus() == .notDetermined {
  locationManager.requestWhenInUseAuthorization()
} else {
  locationManager.requestLocation()
}

Taking each numbered section in turn:

  1. Before using the location manager, it’s good practice to make sure the user has enabled location services.
  2. When you geocode the location’s coordinates, you’ll probably lose some precision. An accuracy of 100 meters is more than adequate.
  3. The location manager’s delegate informs the app when new locations arrive and when the privacy setting changes.
  4. If the user enabled and authorized location services, you request the current location.

Build and run. Did you get an alert asking for your authorization? No.

Confused face

That’s because there’s one more thing left to take care of: You need to tell the user why you’re making the request.

Autocompleting the User’s Location

You want your app to be able to make sensible suggestions about where the user might want to go. To do this, it needs to know where the user currently is. So you need to get permission to access the user’s current location.

Getting Authorization to Access the User’s Location

Open Supporting InfoInfo.plist and follow these steps:

  1. Add NSLocationWhenInUseUsageDescription to Info.plist.
  2. Keep the Type as String.
  3. Set the Value to the message to show users, which explains why you’re asking for their location: Used to autofill the start/end location.

Xcode-plist with NSLocationWhenInUseUsageDescription highlighted

Build and run again; an alert should now pop up, as expected.

Location request alert in an iPhone

Tap Allow While Using App or Allow Once to make the location manager aware of your location.

NSLocationAlwaysUsageDescription and requestAlwaysAuthorization() lets the app access the user’s location, even when the app is running in the background.

Note: NSLocationWhenInUseUsageDescription and requestWhenInUseAuthorization() lets the app access the user’s location while the app is running. Starting in iOS 13, when requesting this permission level, there’s a third option: Allow Once. This option sets CLAuthorizationStatus to authorizedWhenInUse while the app is running. The next time the app launches, it resets the authorization to notDetermined.

Now that you have permission to use the user’s location, it’s time to put that information to work!

Turning the User’s Coordinates Into an Address

The location information that your app receives will be in the form of coordinates — but most users want to enter their routes as an address.

Next, you’ll create a CLGeocoder to reverse geocode the user’s location. Reverse geocoding is the process of turning a location’s coordinates into a human-readable address.

Add a new property to the top of ViewControllers/RouteSelectionViewController.swift:

private var currentPlace: CLPlacemark?

Here, you use currentPlace to store the geocoded information from the current location. You’ll use this information later to generate a route.

Scroll to the end of CLLocationManagerDelegate and replace the placeholder methods with:

func locationManager(
  _ manager: CLLocationManager, 
  didChangeAuthorization status: CLAuthorizationStatus
) {
  // 1
  guard status == .authorizedWhenInUse else {
    return
  }
  manager.requestLocation()
}

func locationManager(
  _ manager: CLLocationManager, 
  didUpdateLocations locations: [CLLocation]
) {
  guard let firstLocation = locations.first else {
    return
  }

  // TODO: Configure MKLocalSearchCompleter here...

  // 2
  CLGeocoder().reverseGeocodeLocation(firstLocation) { places, _ in
    // 3
    guard
      let firstPlace = places?.first, 
      self.originTextField.contents == nil 
      else {
        return
    }
    

    // 4
    self.currentPlace = firstPlace
    self.originTextField.text = firstPlace.abbreviation
  }
}

Here’s what this code does:

  1. Ensure the user has given the app authorization to access location information.
  2. reverseGeocodeLocation(_:completionHandler:) returns an array of placemarks in its completion handler. For most geocoding results, this array will only contain one element. In rare situations, a single location can return many nearby locations. In this case, places?.first suffices.
  3. Since the user can edit the origin text field, it’s a good idea to make sure that it’s empty before changing it.
  4. Store the current location and update the field.
Note: firstPlace.abbreviation is an extension property that determines an appropriate description for a given CLPlacemark.

By default, the simulator doesn’t have a default location set. To configure the location, open the simulator, if it’s not already running. Under FeaturesLocation, select Apple.

Build and run. Once the geocode finishes, you’ll see Apple Campus appear in the Start / End field.

RWRouter initial screen with Apple Campus text filled in the first text field

Next, you’ll need to handle user input by implementing MKLocalSearchCompleter to suggest a location in real time.