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.
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.
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.