Core Location - How to Display a Human-Readable Address Using CLGeocoder
Tags: ios, swift, tutorial
Published: 2020-09-06 03:39:18

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4cwoplqprflr3z6nb8vt.png)
### Exordium

In a previous post, I wrote about setting up Core Location in a UIKit application. I covered adding the usage descriptions to the Info.plist file, requesting location authorization, and pulling the location from the `CLLocationManager`. If you need some help setting up Core Location in your UIKit app, take a look at that post: [https://rustynailsoftware.com/dev-blog/core-location-setting-up-core-location-with-uikit](https://rustynailsoftware.com/dev-blog/core-location-setting-up-core-location-with-uikit).

In this post, I'll be going over how to implement `CLGeocoder` - a class in Core Location that helps developers produce human-readable versions of geographic coordinates in their iOS apps. I'll also briefly cover the `CLLocation` object, as I failed to do so in my first Core Location post. You can follow along with the code I've hosted on GitHub: [https://github.com/andrew-lundy/core-location-tutorial](https://github.com/andrew-lundy/core-location-tutorial)

Let's dive in.

### The CLLocation Class

An essential aspect of Core Location is the `CLLocation` class. It's so essential that I forgot to write about it in my first Core Location series blog post. The `CLLocation` class is an object that holds the device's location information - including the altitude and course information. The course info is the speed and direction of a device. In Core Location, you obtain the location details via the `CLLocationManager` class. Here, I've stored the information in the `currentLocation` variable:
```
let locationManager = CLLocationManager()
let currentLocation = locationManager.location
```
With this, we have gained access to the location details and the ability to pull those details. For example, to obtain the coordinates of the location, use the location manager's `coordinate` attribute:
```
let locationManager = CLLocationManager()
let currentLocation = locationManager.location
// Print the location details.
// Ex: <+37.78735352, -122.40822700> +/- 5.00m (speed - 1.99 mps / course - 1.00) @ 9/5/20, 5:13:46 PM Central Daylight Time
print(currentLocation)

let locationCoordinate = currentLocation.coordinate
// Print the coordinate value of the location as a CLLocationCoordinate2D.
// Ex: CLLocationCoordinate2D(latitude: 37.787353515625, longitude: -122.408227)
print(locationCoordinate)
```

### Reverse-Geocoding with CLGeocoder

As seen above, the `CLLocation` class returns the location's information in a pretty much non-usable format. Sure, we can pull the geographic coordinates and the speed at which the device is moving. But, this information can only be useful in certain situations. What happens when we need to display the location info to users who don't want to read and convert coordinates? The answer is found in Apple's `CLGeocoder` class.


As of now, the `ViewController` class holds the following objects and `IBOutlets`:
```
@IBOutlet weak var changeLocationBttn: UIButton!
@IBOutlet weak var reverseGeocodeLocation: UIButton!
@IBOutlet weak var locationDataLbl: UILabel!

private var locationManager: CLLocationManager!
private var currentLocation: CLLocation!
private var geocoder: CLGeocoder!
```
I am going to go ahead and initialize the CLGeocoder in the viewDidLoad method of the ViewController class:
```
override func viewDidLoad() {
  super.viewDidLoad()
  changeLocationBttn.layer.cornerRadius = 10
  reverseGeocodeLocation.layer.cornerRadius = 10
  reverseGeocodeLocation.titleLabel?.textAlignment = .center
  
  locationManager = CLLocationManager()
  locationManager.delegate = self
  
  // Initialize the Geocoder
  geocoder = CLGeocoder()
}
```

All of the work that the `CLGeocoder` will be doing is going to happen in the `reverseGeocodeLocationBttnTapped` method. The first thing we are going to do is make sure that the `currentLocation` variable is not empty. This is set up to hold the device's location information and is given a value when the app requests authorization status. We need to make this check because there is nothing to reverse-geocode if there is no location value.
```
@IBAction func reverseGeocodeLocationBttnTapped(_ sender: Any) {
    guard let currentLocation = self.currentLocation else {
        print("Unable to reverse-geocode location.")
        return
    }
}
```
To start the process of reverse-geocoding coordinates, you must call the reverseGeocodeLocation method on the `CLGeocoder`. This method takes two parameters - a `CLLocation` object and a `CLGeocodeCompletionHandler`. The completion handler also has two parameters - an array of `CLPlacemark` and an `Error`.
```
// The method that does the reverse-geocoding.
geocoder.reverseGeocodeLocation(location: CLLocation, completionHandler: CLGeocodeCompletionHandler)

// Here is the method when in use.
geocoder.reverseGeocodeLocation(currentLocation) { (placemarks, error) in
  
}
```
The `CLPlacemark` class is new, so let's take a look at it. A `CLPlacemark`, or 'placemark,' holds the human-readable version of a coordinate and gives developers access to information such as the name of a place, the city, state, zip code, and more. You can read more about the `CLPlacemark` class in Apple's docs: [https://developer.apple.com/documentation/corelocation/clplacemark](https://developer.apple.com/documentation/corelocation/clplacemark)

Here are the steps we'll perform in the completion handler:
```
geocoder.reverseGeocodeLocation(currentLocation) { (placemarks, error) in
    // 1
    if let error = error {
        print(error)
    }
            
    // 2
    guard let placemark = placemarks?.first else { return }
    print(placemark)
    // Geary & Powell, Geary & Powell, 299 Geary St, San Francisco, CA 94102, United States @ <+37.78735352,-122.40822700> +/- 100.00m, region CLCircularRegion (identifier:'<+37.78735636,-122.40822737> radius 70.65', center:<+37.78735636,-122.40822737>, radius:70.65m)
            
    // 3
    guard let streetNumber = placemark.subThoroughfare else { return }
    guard let streetName = placemark.thoroughfare else { return }
    guard let city = placemark.locality else { return }
    guard let state = placemark.administrativeArea else { return }
    guard let zipCode = placemark.postalCode else { return }
            
    // 4
    DispatchQueue.main.async {
        self.locationDataLbl.text = "\(streetNumber) \(streetName) \n \(city), \(state) \(zipCode)"
    }
}
```
Check if the handler produces an error. If so, print it to the console. In a real app, you'd handle the error more efficiently.

Use a guard statement to obtain the first placemark returned from the completion handler. For most geocoding requests, the array of placemarks should only contain one entry. I went ahead and printed the placemark data.

Pull specific data out of the placemark, depending on the use case. In this instance, I've pulled the street number, street name, city, state, and zip code.

Finally, I've updated the label in the app with the placemark data. Since this is changing the user interface, I have done this on the main thread using the `DispatchQueue` class.

The `reverseGeocodeLocationBttnTapped` `IBAction` should now look like this:
```
@IBAction func reverseGeocodeLocationBttnTapped(_ sender: Any) {
    guard let currentLocation = self.currentLocation else {
        print("Unable to reverse-geocode location.")
        return
    }
    
    geocoder.reverseGeocodeLocation(currentLocation) { (placemarks, error) in
        if let error = error {
            print(error)
        }
            
        guard let placemark = placemarks?.first else { return }
        guard let streetNumber = placemark.subThoroughfare else { return }
        guard let streetName = placemark.thoroughfare else { return }
        guard let city = placemark.locality else { return }
        guard let state = placemark.administrativeArea else { return }
        guard let zipCode = placemark.postalCode else { return }
  
        DispatchQueue.main.async {
            self.locationDataLbl.text = "\(streetNumber) \(streetName) \n \(city), \(state) \(zipCode)"
        }
    } 
}
```