Reverse geocoding in Swift 4

Adrian picture Adrian · Oct 22, 2017 · Viewed 15.1k times · Source

I'm attempting to write a simple method that's fed CLLocationDegrees and returns a CLPlacemark. Looking at Apple's documentation, it seems like a simple task.

Below is what I've dumped into a playground:

import CoreLocation
// this is necessary for async code in a playground
import PlaygroundSupport 

// this is necessary for async code in a playground
PlaygroundPage.current.needsIndefiniteExecution = true

func geocode(latitude: CLLocationDegrees, longitude: CLLocationDegrees) -> CLPlacemark? {
  let location = CLLocation(latitude: latitude, longitude: longitude)
  let geocoder = CLGeocoder()

  var placemark: CLPlacemark?

  geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
    if error != nil {
      print("something went horribly wrong")
    }

    if let placemarks = placemarks {
      placemark = placemarks.first
    }
  }

  return placemark
}

let myPlacemark = geocode(latitude: 37.3318, longitude: 122.0312)

As it stands, my method is returning nil. I'm not sure where my error lies, but I rest assured it's something starlingly stupid on my part. Thank you for reading.

Answer

Leo Dabus picture Leo Dabus · Oct 22, 2017
import UIKit
import CoreLocation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

func geocode(latitude: Double, longitude: Double, completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void)  {
    CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude)) { placemark, error in
        guard let placemark = placemark, error == nil else {
            completion(nil, error)
            return
        }
        completion(placemark, nil)
    }
}

or simply:

func geocode(latitude: Double, longitude: Double, completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void)  {
    CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), completionHandler: completion)
}

or extending CLLocation:

extension CLLocation {
    func geocode(completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void)  {
        CLGeocoder().reverseGeocodeLocation(self, completionHandler: completion)
    }
}

To format your place mark as a mailing address you can use Contacts framework CNPostalAddressFormatter:

import Contacts

extension Formatter {
    static let mailingAddress: CNPostalAddressFormatter = {
        let formatter = CNPostalAddressFormatter()
        formatter.style = .mailingAddress
        return formatter
    }()
}

extension CLPlacemark {
    var mailingAddress: String? {
        return postalAddress?.mailingAddress
    }
}

extension CNPostalAddress {
    var mailingAddress: String {
        return Formatter.mailingAddress.string(from: self)
    }
}

placemark

Contains an array of CLPlacemark objects. For most geocoding requests, this array should contain only one entry. However, forward-geocoding requests may return multiple placemark objects in situations where the specified address could not be resolved to a single location. If the request was canceled or there was an error in obtaining the placemark information, this parameter is nil.

For more information about the CLPlacemark properties you can check this CLPlacemark


Usage:

let location = CLLocation(latitude: -22.963451, longitude: -43.198242)
location.geocode { placemark, error in
    if let error = error as? CLError {
        print("CLError:", error)
        return
    } else if let placemark = placemark?.first {
        // you should always update your UI in the main thread
        DispatchQueue.main.async {
            //  update UI here
            print("name:", placemark.name ?? "unknown")

            print("address1:", placemark.thoroughfare ?? "unknown")
            print("address2:", placemark.subThoroughfare ?? "unknown")
            print("neighborhood:", placemark.subLocality ?? "unknown")
            print("city:", placemark.locality ?? "unknown")

            print("state:", placemark.administrativeArea ?? "unknown")
            print("subAdministrativeArea:", placemark.subAdministrativeArea ?? "unknown")
            print("zip code:", placemark.postalCode ?? "unknown")
            print("country:", placemark.country ?? "unknown", terminator: "\n\n")

            print("isoCountryCode:", placemark.isoCountryCode ?? "unknown")
            print("region identifier:", placemark.region?.identifier ?? "unknown")

            print("timezone:", placemark.timeZone ?? "unknown", terminator:"\n\n")

            // Mailind Address
            print(placemark.mailingAddress ?? "unknown")
        }
    }
}

This will print

name: Morro da Saudade
address1: Rua Casuarina
address2: 597
neighborhood: Lagoa
city: Rio de Janeiro
state: RJ
subAdministrativeArea: unknown
zip code: 22011-040
country: Brazil

isoCountryCode: BR
region identifier: <-22.96345100,-43.19824200> radius 141.83
timezone: America/Sao_Paulo (current)

Rua Casuarina, 597

Lagoa

Rio de Janeiro RJ

22011-040

Brazil