The CLGeocoder class is part of the Core Location framework and it has been around since iOS 5 and macOS 10.8. Initially, the functionality of the CLGeocoder class was limited to reverse geocoding addresses into coordinates. Since iOS 8 and macOS 10.10, however, it is also possible to fetch the coordinates of addresses. In this tutorial, you learn how to do both using Xcode 9 and Swift 4.

Project Setup

Launch Xcode and create a new application based on the Tabbed Application template.

Setting Up the Project In Xcode

Name the project Geocoding, set Language to Swift, and Devices to iPhone. Tell Xcode where you would like to save the project and click Create.

Setting Up the Project In Xcode

Before we start working with the CLGeocoder class, we need to change a few things. Xcode has created two UIViewController subclasses for us, FirstViewController and SecondViewController. Rename FirstViewController to ForwardGeocodingViewController and SecondViewController to ReverseGeocodingViewController. This also means you need to update the file names of these classes and the class names in Main.storyboard.

Build and run your application to make sure you didn't break anything.

Forward Geocoding With Swift

We start with forward geocoding an address. This means that we ask the user to enter an address for which the application fetches the coordinates.

Creating the User Interface

Open ForwardGeocodingViewController.swift and create six outlets as shown below. The text fields are used for entering the country, city, and street of the location. The application uses these values to forward geocode the coordinates of the location.

The label displays the result of the geocoding operation. The button and activity indicator view are used for starting the geocoding operation and showing its progress.

import UIKit

class ForwardGeocodingViewController: UIViewController {

    // MARK: - Properties

    @IBOutlet var countryTextField: UITextField!
    @IBOutlet var streetTextField: UITextField!
    @IBOutlet var cityTextField: UITextField!

    @IBOutlet var geocodeButton: UIButton!
    @IBOutlet var activityIndicatorView: UIActivityIndicatorView!

    @IBOutlet var locationLabel: UILabel!

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

We also create an action, which is invoked when the user taps the button. This action validates the input and starts the geocoding operation. We leave the implementation empty for now.

// MARK: - Actions

@IBAction func geocode(_ sender: UIButton) {

}

As you can see, the user interface is pretty basic. Make sure you connect the outlets to the corresponding user interface elements and don't forget to wire up the button's touch up inside event with the geocode(_:) action.

User Interface for the Forward Geocoding View Controller With CLGeocoder

Validating the Input

We only want to start a geocoding operation if the user's input is valid. Open ForwardGeocodingViewController.swift and update geocode(_:) as follows.

@IBAction func geocode(_ sender: UIButton) {
    guard let country = countryTextField.text else { return }
    guard let street = streetTextField.text else { return }
    guard let city = cityTextField.text else { return }

    print("\(country), \(city), \(street)")
}

Build and run the application to give it a try.

Forward Geocoding

The API of the CLGeocoder class is short and easy to use. We have three options to forward geocode the address the user entered in the text fields.

  • geocodeAddressString(_:completionHandler:)
  • geocodeAddressString(_:in:completionHandler:)
  • geocodeAddressDictionary(_:completionHandler:)

The geocodeAddressString(_:completionHandler:) method is the easiest to use, but it is also most prone to errors. Why is that? We hand the CLGeocoder class an address as a string. It is up to the Core Location framework, and the web service it communicates with, to figure out which address components the string contains.

A better option is to use the geocodeAddressString(_:in:completionHandler:) method. In addition to the address string, the method accepts a CLRegion instance to further narrow down the location the user is interested in. This is very useful because it avoids mismatches caused by similarly named locations.

The third option is to use the geocodeAddressDictionary(_:completionHandler:) method. This method allows us to specify an address dictionary as defined by the AddressBook framework, giving the Core Location framework more accurate information about the location we are interested in.

For this tutorial, we use the geocodeAddressString(_:completionHandler:) method. Take a look at the updated implementation of the geocode(_:) method below.

@IBAction func geocode(_ sender: UIButton) {
    guard let country = countryTextField.text else { return }
    guard let street = streetTextField.text else { return }
    guard let city = cityTextField.text else { return }

    // Create Address String
    let address = "\(country), \(city), \(street)"

    // Geocode Address String
    geocoder.geocodeAddressString(address) { (placemarks, error) in
        // Process Response
        self.processResponse(withPlacemarks: placemarks, error: error)
    }

    // Update View
    geocodeButton.isHidden = true
    activityIndicatorView.startAnimating()
}

We create an address string from the user's input and invoke geocodeAddressString(_:completionHandler:) on a CLGeocoder instance. The completion handler, the second parameter of geocodeAddressString(_:completionHandler:), accepts two parameters, an optional array of CLPlacemark instances and an optional Error object. In the completion handler, we invoke a helper method, processResponse(withPlacemarks:error:).

After starting the geocoding operation, we hide the button and show the user the activity indicator view. This shows the user the progress of the geocoding operation.

To make this work we need to take care of three more details, import the Core Location framework, create a CLGeocoder instance, and implement the processResponse(withPlacemarks:error:) method.

At the top of ForwardGeocodingViewController.swift, add an import statement for the Core Location framework.

import UIKit
import CoreLocation

class ForwardGeocodingViewController: UIViewController {

    ...

}

Below the outlets we declared earlier, declare a lazy property, geocoder, of type CLGeocoder.

lazy var geocoder = CLGeocoder()

While the implementation of processResponse(withPlacemarks:error:) is not difficult, you can see that we need to make several checks before we can access the coordinates of the location we are interested in. Why is that?

private func processResponse(withPlacemarks placemarks: [CLPlacemark]?, error: Error?) {
    // Update View
    geocodeButton.isHidden = false
    activityIndicatorView.stopAnimating()

    if let error = error {
        print("Unable to Forward Geocode Address (\(error))")
        locationLabel.text = "Unable to Find Location for Address"

    } else {
        var location: CLLocation?

        if let placemarks = placemarks, placemarks.count > 0 {
            location = placemarks.first?.location
        }

        if let location = location {
            let coordinate = location.coordinate
            locationLabel.text = "\(coordinate.latitude), \(coordinate.longitude)"
        } else {
            locationLabel.text = "No Matching Location Found"
        }
    }
}

First, we might not get any results from the web service the Core Location framework talks to hence the optional array of CLPlacemark instances. If the user entered a bogus address or made a typo, the Core Location framework might not be able to find a location for the address.

Second, we receive an array of CLPlacemark instances. A CLPlacemark is nothing more than a class that encapsulates the information for a particular location. But note that I wrote array of CLPlacemark instances. It is possible that the Core Location framework returns multiple results. If the query of the user was too vague or if multiple locations exist with a similar address, we might receive multiple matches. To keep things simple, we select the first CLPlacemark instance in processResponse(withPlacemarks:error:).

We take a closer look at the CLPlacemark class later in this tutorial. For this example, we only need the location property of the first placemark, which is an instance of the CLLocation class. The coordinate property, an instance of the CLLocationCoordinate2D structure, gives us access to the latitude and longitude of the location.

Run the application and take it for a spin. Even though we use the Core Location framework, note that the user doesn't have to grant the application access to location services.

Screenshot Forward Geocoding iOS Using CLGeocoder, Xcode 9, and Swift 4

What's Next?

In the next tutorial, we take a look at reverse geocoding a set of coordinates. This is very similar in that we make use of the same components, such as CLPlacemark and CLLocation.