In the previous tutorial, we explored forward geocoding with the CLGeocoder class, a member of the Core Location framework. This tutorial zooms in on reverse geocoding, another capability of the CLGeocoder class.

If you want to follow along, download the source files at the bottom of this tutorial.

Reverse Geocoding With Swift

Reverse geocoding is a simple concept. We hand the CLGeocoder class a set of coordinates, latitude and longitude, and ask it for the corresponding address, a physical location that has meaning to the user. The CLGeocoder class exposes one method to accomplish this task, reverseGeocodeLocation(_:completionHandler:). We take a closer look at this method in a few moments.

Creating the User Interface

Open ReverseGeocodingViewController.swift and create five outlets as shown below. The text fields are used for entering the latitude and longitude. The application uses these values to reverse geocode the address 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 ReverseGeocodingViewController: UIViewController {

    // MARK: - Properties

    @IBOutlet var latitudeTextField: UITextField!
    @IBOutlet var longitudeTextField: 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) {

}

The user interface is basic. Don't forget to connect the outlets and the action we created earlier. The geocode(_:) action should be connected to the button's touch up inside event.

User Interface for the Reverse Geocoding View Controller

Validating the Input

We only want to start a geocoding operation if the user's input is valid. This means that the latitude and longitude need to be valid numbers. Open ReverseGeocodingViewController.swift and update the geocode(_:) method as shown below.

@IBAction func geocode(_ sender: UIButton) {
    guard let latAsString = latitudeTextField.text, let lat = Double(latAsString) else { return }
    guard let lngAsString = longitudeTextField.text, let lng = Double(lngAsString) else { return }

    print("\(lat), \(lng)")
}

Build and run the application to give it a try.

Reverse Geocoding

As you learned in the previous tutorial, the API of the CLGeocoder class is concise and easy to use. We only have one option to reverse geocode the coordinates the user entered in the text fields.

The reverseGeocodeLocation(_:completionHandler:) method is the one we are interested in. This method accepts a location, a CLLocation instance, and a completion handler. The completion handler accepts two arguments, an optional array of CLPlacemark instances and an optional Error instance.

The completed implementation of the geocode(_:) method looks similar to the one we implemented in the ForwardGeocodingViewController class. The difference is that we create a CLLocation instance from the coordinates the user entered in the text field and pass the location to the reverseGeocodeLocation(_:completionHandler:) method. In the completion handler, we invoke a helper method that processes the results of the geocoding operation.

@IBAction func geocode(_ sender: UIButton) {
    guard let latAsString = latitudeTextField.text, let lat = Double(latAsString) else { return }
    guard let lngAsString = longitudeTextField.text, let lng = Double(lngAsString) else { return }

    // Create Location
    let location = CLLocation(latitude: lat, longitude: lng)

    // Geocode Location
    geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
        // Process Response
        self.processResponse(withPlacemarks: placemarks, error: error)
    }

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

As in the geocode(_:) method of the ForwardGeocodingViewController class, we hide the button and show the user the activity indicator view when the geocoding operation is kicked off to indicate that the operation is in progress.

Remember from the previous tutorial that we need to import the Core Location framework, create a CLGeocoder instance, and implement the processResponse(withPlacemarks:error:) method.

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

import UIKit
import CoreLocation

class ReverseGeocodingViewController: UIViewController {

    ...

}

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

lazy var geocoder = CLGeocoder()

The implementation of processResponse(withPlacemarks:error:) is similar to that of the ForwardGeocodingViewController class. The main difference is that we are interested in the CLPlacemark instance itself, not its location property.

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

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

    } else {
        if let placemarks = placemarks, let placemark = placemarks.first {
            locationLabel.text = placemark.compactAddress
        } else {
            locationLabel.text = "No Matching Addresses Found"
        }
    }
}

Remember, it is possible that we don't receive any results from the web service the Core Location framework talks to. In that scenario, we display a message to the user.

As you learned in the previous tutorial, the CLGeocoder class returns an array of CLPlacemark instances. We are only interested in the first item.

If we receive a valid response from the web service, we show the first result to the user by asking the CLPlacemark instance for its compactAddress property. This is a convenience property, a computed property, I added in an extension for CLPlacemark.

Before we take a look at the implementation of the compactAddress property, I need to tell you a bit about the CLPlacemark class. A CLPlacemark instance associates data with a set of coordinates. It stores data, such as country, city, and street. But it can also include other data, such as points of interest. There are many use cases for the CLPlacemark class.

Let us now take a look at the implementation of the compactAddress property. Most of the properties of the CLPlacemark class are optional, which is why I created a convenience property. It allows me to build up the address with the properties that have a valid value.

extension CLPlacemark {

    var compactAddress: String? {
        if let name = name {
            var result = name

            if let street = thoroughfare {
                result += ", \(street)"
            }

            if let city = locality {
                result += ", \(city)"
            }

            if let country = country {
                result += ", \(country)"
            }

            return result
        }

        return nil
    }

}

The names of the properties of the CLPlacemark class may look a bit odd. The reason is that not every country has, for example, the concept of a state or province. The documentation can help you find out what each of the properties stands for.

Because the properties we access are optionals, we check if they have a value and construct an address string. This address string is returned as the result. I have added the extension to ReverseGeocodingViewController.swift, but you can put it in its own file if you like.

Run the application and give it a try.

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

Even though this tutorial asks the user for a set of coordinates, that is a pretty uncommon use case. Reverse geocoding is more useful in scenarios in which you want to show the user an address or location description based on its current position or a location the user selects on a map in your application.

What's Next?

You should now have a better understanding of the CLGeocoder class and what you can use it for. Forward and reverse geocoding are not difficult to implement. The Core Location framework makes it very easy thanks to a clear and concise API.