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
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)
}
})
}
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)
})
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
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
}
}
Related Topics
Use a Generic Class as a Custom View in Interface Builder
Nsxmlparser: Unexpected Result with Non-Ascii Characters
Uitableview Dynamic Cell Heights Only Correct After Some Scrolling
Custom Font Sizing in Xcode 6 Size Classes Not Working Properly with Custom Fonts
Localizing Strings in iOS: Default (Fallback) Language
Is a Date in Same Week, Month, Year of Another Date in Swift
Could Not Load the "" Image Referenced from a Nib in the Bundle with Identifier
Install Apps on Device Without Itunes
Move a Node to Finger Using Swift + Spritekit
Swift: How to Open a New App When Uibutton Is Tapped
Auto Login Dropbox Account on Core API Without Login Prompt
Use Didselectrowatindexpath or Prepareforsegue Method for Uitableview
Uitabbar Items Jumping on Back Navigation on iOS 12.1
How to Run the iOS 7.1 Simulator in Xcode 7.0 Beta 2
Launch Image Not Showing Up in iOS Application (Using Images.Xcassets)
Implement Dark Mode Switch in Swiftui App
Instantiate View Controller from Storyboard VS. Creating New Instance