Reverse Geocode Location in Swift

Reverse geocoding in Swift 4

CLGeocoder methods are asynchronous. What you would need a completion handler:

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 placemark 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? {
postalAddress?.mailingAddress
}
}

extension CNPostalAddress {
var mailingAddress: String {
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

Reverse Geocode Location in Swift

you never reverse geocode the location but you pass in manager.location.

see:
CLGeocoder().reverseGeocodeLocation(manager.location, ...

I assume that was a copy&paste mistake and that this is the issue - the code itself looks good - almost ;)

working code

    var longitude :CLLocationDegrees = -122.0312186
var latitude :CLLocationDegrees = 37.33233141

var location = CLLocation(latitude: latitude, longitude: longitude) //changed!!!
println(location)

CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
println(location)
guard error == nil else {
println("Reverse geocoder failed with error" + error.localizedDescription)
return
}
guard placemarks.count > 0 else {
println("Problem with the data received from geocoder")
return
}
let pm = placemarks[0] as! CLPlacemark
println(pm.locality)
})

Swift - Generate an Address Format from Reverse Geocoding

func getAddressFromLatLon(pdblLatitude: String, withLongitude pdblLongitude: String) {
var center : CLLocationCoordinate2D = CLLocationCoordinate2D()
let lat: Double = Double("\(pdblLatitude)")!
//21.228124
let lon: Double = Double("\(pdblLongitude)")!
//72.833770
let ceo: CLGeocoder = CLGeocoder()
center.latitude = lat
center.longitude = lon

let loc: CLLocation = CLLocation(latitude:center.latitude, longitude: center.longitude)

ceo.reverseGeocodeLocation(loc, completionHandler:
{(placemarks, error) in
if (error != nil)
{
print("reverse geodcode fail: \(error!.localizedDescription)")
}
let pm = placemarks! as [CLPlacemark]

if pm.count > 0 {
let pm = placemarks![0]
print(pm.country)
print(pm.locality)
print(pm.subLocality)
print(pm.thoroughfare)
print(pm.postalCode)
print(pm.subThoroughfare)
var addressString : String = ""
if pm.subLocality != nil {
addressString = addressString + pm.subLocality! + ", "
}
if pm.thoroughfare != nil {
addressString = addressString + pm.thoroughfare! + ", "
}
if pm.locality != nil {
addressString = addressString + pm.locality! + ", "
}
if pm.country != nil {
addressString = addressString + pm.country! + ", "
}
if pm.postalCode != nil {
addressString = addressString + pm.postalCode! + " "
}

print(addressString)
}
})

}

How to reverse geocode latitude and longitude from location stored in CloudKit? - Swift 3

Apple provides a method built into Core Location's CLGeocoder class. Here are the docs. If successful the completion handler will give you access to an array of CLPlacemark, so you can grab one of those and access whichever human-readable elements you need. The names of the variables are pretty generic to cover locations all over the world, so you'll have to dig in a bit to find exactly what you need. Check the docs on CLPlacemark for exact details on the variables available to you. In your particular case you'll need locality and administrativeArea for city and state, respectively.

Usage would be something like this:

let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
guard let placemarks = placemarks, let placemark = placemarks.first else { return }
if let city = placemark.locality, let state = placemark.administrativeArea {
//Set your labels or whatever
}
}

Allocating the results of Reverse Geocoding to a global variable

When you call this function the reverseGeocodeLocation runs in the background thread. So if you want to return the address in this method you should use escaping closure.

    func getaddress(_ position:CLLocationCoordinate2D,completion:@escaping (String)->()) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let placemark = placemarks?.first {
let street = placemark.name!
let locality = placemark.locality!
let string = "\(street), \(locality)"
completion(string)
}
}
}

self.getaddress(position.target) { address in
print(address)
self.addressTextView.text = address
}

Google Maps iOS Reverse Geocoding

So, I ended up going another route. It's not pretty, but it works. Each GMSMarker has a "userData" attribute that can be used to pass data. What I did was moved the marker creation into the reverse geocode completion handler and assigned the address to the "userData" attribute. Then, when the user taps to show the current address, the reverse geocode is kicked off, the marker is created and placed on the map.

geocoder.reverseGeocodeCoordinate(position) { response, error in
if let location = response?.firstResult() {
let marker = GMSMarker(position: position)
let lines = location.lines! as [String]

marker.userData = lines.joined(separator: "\n")
marker.title = lines.joined(separator: "\n")
marker.infoWindowAnchor = CGPoint(x: 0.5, y: -0.25)
marker.accessibilityLabel = "current"
marker.map = self.mapView

self.mapView.animate(toLocation: position)
self.mapView.selectedMarker = marker
}
}

And when the marker is selected, the label is set to the address as passed in the "userData" attribute:

let infoWindow = Bundle.main.loadNibNamed("InfoWindowCurrent", owner: self, options: nil)?[0] as! InfoWindowCurrent

infoWindow.labelAddress.text = marker.userData as? String

return infoWindow


Related Topics



Leave a reply



Submit