Swift - CLGeocoder reverseGeocodeLocation completionHandler closure - ios

What I'm trying to do is pass a CLLocation to the function getPlacemarkFromLocation which then uses the passed CLLocation through reverseGeocodeLocation to set the CLPlacemark? that will be returned.
I'm having issues creating the completionHandler closure in reverseGeocodeLocation, it's throwing a compiler error/crash:
In Swift, CLGeocodeCompletionHandler is CLGeocodeCompletionHandler = (AnyObject[]!, NSError!) -> Void according to the documentation AnyObject[]! is supposed to contain CLPlacemark objects just like the Objective-C version.
Here's my current code:
class func getPlacemarkFromLocation(location:CLLocation)->CLPlacemark?{
var g = CLGeocoder()
var p:CLPlacemark?
g.reverseGeocodeLocation(location, completionHandler: {
(placemarks, error) in
let pm = placemarks as? CLPlacemark[]
if (pm && pm?.count > 0){
p = placemarks[0] as? CLPlacemark
}
})
return p?
}
EDIT: It seems like the error had to do with placemarks.count with placemarks not being treated like an array. It compiles now, however I'm getting nothing but nil when trying to set p inside the completionHandler. I've checked the CLLocations being passed and they are valid.
EDIT 2: After printing placemarks, I can confirm that it returns data. However p is still returning nil.

I found the answer I needed in this thread: Set address string with reverseGeocodeLocation: and return from method
The issue lies with the fact that reverseGeocodeLocation is asynchronous, the method is returning a value before the completionBlock sets p in my example.
As requested, here's my current code.
func showAddViewController(placemark:CLPlacemark){
self.performSegueWithIdentifier("add", sender: placemark)
}
func getPlacemarkFromLocation(location: CLLocation){
CLGeocoder().reverseGeocodeLocation(location, completionHandler:
{(placemarks, error) in
if error {println("reverse geodcode fail: \(error.localizedDescription)")}
let pm = placemarks as [CLPlacemark]
if pm.count > 0 { self.showAddPinViewController(placemarks[0] as CLPlacemark) }
})
}
I didn't want to take the NSNotificationCenter route because that would add unnecessary overhead, rather inside the completionHandler closure I call upon another function and pass the CLPlacemark generated by getPlacemarkFromLocation as a parameter to keep things asynchronous since the function will be called after placemarks is set the function (should) receive the placemark needed and execute the code you want. Hope what I said makes sense.

With these lines of Swift, you can print out fully the location's address:
func getLocationAddress(location:CLLocation) {
var geocoder = CLGeocoder()
println("-> Finding user address...")
geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error)->Void in
var placemark:CLPlacemark!
if error == nil && placemarks.count > 0 {
placemark = placemarks[0] as CLPlacemark
var addressString : String = ""
if placemark.ISOcountryCode == "TW" /*Address Format in Chinese*/ {
if placemark.country != nil {
addressString = placemark.country
}
if placemark.subAdministrativeArea != nil {
addressString = addressString + placemark.subAdministrativeArea + ", "
}
if placemark.postalCode != nil {
addressString = addressString + placemark.postalCode + " "
}
if placemark.locality != nil {
addressString = addressString + placemark.locality
}
if placemark.thoroughfare != nil {
addressString = addressString + placemark.thoroughfare
}
if placemark.subThoroughfare != nil {
addressString = addressString + placemark.subThoroughfare
}
} else {
if placemark.subThoroughfare != nil {
addressString = placemark.subThoroughfare + " "
}
if placemark.thoroughfare != nil {
addressString = addressString + placemark.thoroughfare + ", "
}
if placemark.postalCode != nil {
addressString = addressString + placemark.postalCode + " "
}
if placemark.locality != nil {
addressString = addressString + placemark.locality + ", "
}
if placemark.administrativeArea != nil {
addressString = addressString + placemark.administrativeArea + " "
}
if placemark.country != nil {
addressString = addressString + placemark.country
}
}
println(addressString)
}
})
}
Cheers!

Here is closure that worked for me -- it took awhile to get it to work. I think your problem is related to not initializing p with the correct initializer. I tried a few variations until I got this to work: self.placemark = CLPlacemark(placemark: stuff[0] as CLPlacemark)
geocoder.reverseGeocodeLocation(newLocation, completionHandler: {(stuff, error)->Void in
if error {
println("reverse geodcode fail: \(error.localizedDescription)")
return
}
if stuff.count > 0 {
self.placemark = CLPlacemark(placemark: stuff[0] as CLPlacemark)
self.addressLabel.text = String(format:"%# %#\n%# %# %#\n%#",
self.placemark.subThoroughfare ? self.placemark.subThoroughfare : "" ,
self.placemark.thoroughfare ? self.placemark.thoroughfare : "",
self.placemark.locality ? self.placemark.locality : "",
self.placemark.postalCode ? self.placemark.postalCode : "",
self.placemark.administrativeArea ? self.placemark.administrativeArea : "",
self.placemark.country ? self.placemark.country : "")
}
else {
println("No Placemarks!")
return
}
})
EDIT:
moved better answer to its own answer.

EDIT: This doesn't work. The value is nil outside the closure -- see comments below
Your p is nil because the closure is capturing it before it is initialized to a reference. To get the behavior you want you need to make p a non-optional value such as var p : CLPlacemark!.
Below is code I used to test my conjecture:
func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
var g = CLGeocoder()
var p:CLPlacemark?
let mynil = "empty"
g.reverseGeocodeLocation(newLocation, completionHandler: {
(placemarks, error) in
let pm = placemarks as? CLPlacemark[]
if (pm && pm?.count > 0){
// p = CLPlacemark()
p = CLPlacemark(placemark: pm?[0] as CLPlacemark)
println("Inside what is in p: \(p?.country ? p?.country : mynil)")
}
})
println("Outside what is in p: \(p?.country ? p?.country : mynil)")
}
Here is console log:
Pushit <- button pressed to start location capturing
Outside what is in p: empty
Inside what is in p: United States
Outside what is in p: empty
Inside what is in p: United States
Outside what is in p: empty...

Bit late to this party, but it looks like you need(ed) to do some ground-up reading about async stuff. Saying that, you've probably learnt it by now.
The basic problem with your code is that p (your placemark) is being set after the function returns, so it's just lost - you can't use a function to return a value with async. With a completion closure, your code is passed the placemark when it arrives (asynchronously) & the closure is invoked - note the function is now returning nothing.
func getPlacemarkFromLocation(_ location: CLLocation, completion: ((CLPlacemark?) -> ())) {
CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) in
// use optional chaining to safely return a value or nil
// also using .first rather than checking the count & getting placemarks[0] -
// if the count is zero, will just give you nil
// probably a good idea to check for errors too
completion(placemarks?.first)
})
}
Use -
getPlacemarkFromLocation(myLocation, completion: { (placemark) in
// do something with the placemark here
})
I've not actually put this into Xcode, but it looks right...

Your stuff doesn't work for a number of reasons. Here's the part that I fixed without actually looking at the functionality:
class func getPlacemarkFromLocation(location:CLLocation)->CLPlacemark?{
var g = CLGeocoder()
var p:CLPlacemark?
g.reverseGeocodeLocation(location, completionHandler: {
(placemarks, error) in
let pm = placemarks!
if (pm.count > 0){
p = placemarks![0]
}
})
return p
}

Related

CLGeocoding, facing problem when reverse geocode and getting the address from LAT LONG to show in tableView [duplicate]

This question already has answers here:
How do i return coordinates after forward geocoding?
(3 answers)
block until reverseGeocode has returned
(1 answer)
Closed 3 years ago.
I am new swift. I have a task that need to complete. I am getting response from server, where I get numbers of Latitude and Longitude.
{
EndLat = "28.511593";
EndtLong = "77.071136";
},
{
EndLat = "28.511593";
EndtLong = "77.071136";
},.....
getting this type of response having more than 100s of entry.
Now when I'm fetching the address through reverse geocoding..
func getAddressForLogOut(pdblLatitude: String, withLongitude pdblLongitude: String) {
if (pdblLatitude != "") && (pdblLongitude != "") {
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)
self.endAdd = addressString
print("self.endAdd is",self.endAdd)
}
} )
}
}
I'm using that function in tableView's cellForRowAtIndexPath functon. when i scroll the table view the app get crash. getting nil at placemark!
let pm = placemarks! as [CLPlacemark] at this line.
Can any help me to use the reverse geocoding that let me fetching the address of more than 100s of latitude and longitude and show it up in tableView.
CLGeocoder returns an optional type:
typealias CLGeocodeCompletionHandler = ([CLPlacemark]?, Error?) -> Void
(from doc)
you should check it.
I think that usage a cell for data loading from geocoder is bad idea, because you will have a race:
start request1(lat1,lon1)
reusing cell
start request2(lat2,lon2)
response from geocoder for request1
At 4) a cell shows inconsistent data.

How to get the address from the coordinate

I want to get the address from the coordinate. I have attached my code below..
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last!
let latvalue = lastLocation.coordinate.latitude
let lngvalue = lastLocation.coordinate.longitude
self.db_latvalue = latvalue
self.db_lngvalue = lngvalue
let location = CLLocation(latitude: latvalue, longitude:lngvalue)
let address = CLGeocoder.init()
address.reverseGeocodeLocation(CLLocation.init(latitude: latvalue, longitude:lngvalue)) { (places, error) in
if error == nil{
if let place = places{
print("addressshowingssq \(place)")
self.db_address = "\(place)"
}
}
}
Output:
[L-30 2nd A Main Road, L-30 2nd A Main Road, HSR Layout, Bengaluru,
Karnataka 560102, India # <+12.91597974,+77.62879254> +/- 100.00m,
region CLCircularRegion (identifier:'<+12.91597974,+77.62879254>
radius 70.94', center:<+12.91597974,+77.62879254>, radius:70.94m)]
I want only the address as i mention below
L-30 2nd A Main Road, L-30 2nd A Main Road, HSR Layout, Bengaluru,
Karnataka 560102
I researched google i got different solution so i got confused.
Update
I have done a few modification to iVarun's solution. This is simpler. and working.
First, add this function:
func geocode(latitude: Double, longitude: Double, completion: #escaping (CLPlacemark?, Error?) -> ()) {
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude)) { completion($0?.first, $1) }
}
After that,
get the address:
geocode(latitude: latvalue, longitude: lngvalue) { placemark, error in
guard let placemark = placemark, error == nil else { return }
// you should always update your UI in the main thread
DispatchQueue.main.async {
// update UI here
print("address1:", placemark.thoroughfare ?? "")
print("address2:", placemark.subThoroughfare ?? "")
print("city:", placemark.locality ?? "")
print("state:", placemark.administrativeArea ?? "")
print("zip code:", placemark.postalCode ?? "")
print("country:", placemark.country ?? "")
}
}
Result:
address1: Rua Casuarina
address2: 443
city: Rio de Janeiro
state: RJ
zip code: 20975
country: Brazil
As #iOSer indicated, CLPlacemark is capable of giving you this part of the string, However.
You could split the string:
let output:String = "[L-30 2nd A Main Road, L-30 2nd A Main Road, HSR Layout, Bengaluru, Karnataka 560102, India # <+12.91597974,+77.62879254> +/- 100.00m, region CLCircularRegion (identifier:'<+12.91597974,+77.62879254> radius 70.94', center:<+12.91597974,+77.62879254>, radius:70.94m)]"
let items = output.components(separatedBy: "#")
print(items[0])
Becuse the # will be always included, you could skip the rest.
Result:
Hope this will help you:
address.reverseGeocodeLocation(CLLocation.init(latitude: latvalue, longitude:lngvalue)) { (places, error) in
if error == nil{
let placeMark = places! as [CLPlacemark]
if placeMark.count > 0 {
let placeMark = places![0]
var addressString : String = ""
if placeMark.subThoroughfare != nil {
addressString = addressString + placeMark.subThoroughfare! + ", "
}
if placeMark.thoroughfare != nil {
addressString = addressString + placeMark.thoroughfare! + ", "
}
if placeMark.subLocality != nil {
addressString = addressString + placeMark.subLocality! + ", "
}
if placeMark.locality != nil {
addressString = addressString + placeMark.locality! + ", "
}
if placeMark.administrativeArea != nil {
addressString = addressString + placeMark.administrativeArea! + ", "
}
if placeMark.country != nil {
addressString = addressString + placeMark.country! + ", "
}
if placeMark.postalCode != nil {
addressString = addressString + placeMark.postalCode! + " "
}
print(addressString)
}
}
}
Output:
L-30, 2nd A Main Road, HSR Layout, Bengaluru, Karnataka, India, 560102
Swift 3
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
let objLocation = CLLocation(latitude: locValue.latitude, longitude: locValue.longitude)
CLGeocoder().reverseGeocodeLocation(objLocation) { (placemarksArray, error) in
if error != nil {
print("Reverse geocoder failed with error" + (error?.localizedDescription)!)
return
}
if (placemarksArray?.count)! > 0 {
let objPlacemark = placemarksArray?[0]
self.generateAddress(objPlacemark: objPlacemark!)
self.locationManager?.stopUpdatingLocation()
self.locationManager = nil
}
else {
print("Problem with the data received from geocoder")
}
}
}
Function Parsing placemark to string...
func generateAddress(objPlacemark : CLPlacemark) -> String {
print("objPlacemark : \(objPlacemark.description)")
var completeAddress = ""
if objPlacemark.name != nil {
completeAddress = String(describing: objPlacemark.name!)
}
if objPlacemark.thoroughfare != nil && (objPlacemark.name != objPlacemark.thoroughfare) {
completeAddress = completeAddress + ", " + String(describing: objPlacemark.thoroughfare!)
}
if objPlacemark.subThoroughfare != nil {
completeAddress = completeAddress + ", " + String(describing: objPlacemark.subThoroughfare!)
}
if objPlacemark.subLocality != nil {
completeAddress = completeAddress + "," + String(describing: objPlacemark.subLocality!)
}
if objPlacemark.locality != nil {
completeAddress = String(describing: objPlacemark.locality!)
}
if objPlacemark.postalCode != nil {
completeAddress = completeAddress + "," + String(describing: objPlacemark.postalCode!)
}
if objPlacemark.administrativeArea != nil {
completeAddress = completeAddress + "," + String(describing: objPlacemark.administrativeArea!)
}
if objPlacemark.isoCountryCode != nil {
completeAddress = completeAddress + "," + String(describing: objPlacemark.isoCountryCode!)
}
print("completeAddress : \(completeAddress)")
return completeAddress
}
CLGeocodeCompletionHandler contains an array of CLPlacemark. You can access its properties such as name, locality, isoCountryCode etc to form a complete address!!

Closure for displaying results of reverseGeocoder in Swift

In response to a user action, I would like to convert a CLLocation into an address string and display it back to the user.
The sequence is therefore
1. User action triggers sequence
2. ReverseGeocoder makes request to Apple's servers and returns results asynchronously.
3. Displays results to user.
I am able to display the results if I have a dedicated method with the display part in the completion block as follows:
func userWantsAddress {
displayAddressFrom(location: myLocation)
}
func displayAddressFrom(location: CLLocation) {
CLGeocoder().reverseGeocodeLocation(location) { (placemark, error) in
if error != nil {
print("error")
} else {
let place = placemark! as [CLPlacemark]
if place.count > 0 {
let place = placemark![0]
var addressString : String = ""
if place.subThoroughfare != nil {
addressString = addressString + place.subThoroughfare! + "\n"
}
if place.thoroughfare != nil {
addressString = addressString + place.thoroughfare! + " in "
}
if place.locality != nil {
addressString = addressString + place.locality!
}
if place.subAdministrativeArea != nil {
addressString = addressString + ", "+place.subAdministrativeArea!
}
//THIS IS WHERE YOU DISPLAY
myLabel.text = "THE LOCATION IS \(addressString)"
print("the location is",addressString)
}
}
}
}
What I would like to do, however, is streamline the code so once the results are obtained, return them in a closure to the calling method for customization so that I can reuse the addressFromString method (and don't have to rewrite it every time I want to convert a location into an address) but I can't figure out the syntax.
I think I need to give the first method a completion block to wait for completion of the second. And have the second return results in a closure: Something like:
func userWantsAddress(location: myLocation completion:#escaping (_ response:String)->()){
displayAddressFrom(location: myLocation completion:completion) {
completion("HERE IS YOUR ADDRESS")
}
However, I can't seem to get this right
Would be grateful for any suggestions on how to do this.
You just need to add a completion handler to your method and return the first placemark when calling completion:
func displayAddressFrom(location: CLLocation, completion: #escaping (CLPlacemark?, Error?) -> ()) {
CLGeocoder().reverseGeocodeLocation(location) {
completion($0?.first, $1)
}
}
let location = CLLocation(latitude: -22.963451, longitude: -43.198242)
displayAddressFrom(location: location) { placemark, error in
guard let placemark = placemark, error == nil else { return }
// Update your UI from the main thread
DispatchQueue.main.async {
// UI update here
print(placemark)
}
}
Morro da Saudade, Morro da Saudade, Rua Casuarina, 443, Lagoa, Rio de
Janeiro - RJ, 22011-040, Brazil # <-22.96345100,-43.19824200> +/-
100.00m, region CLCircularRegion (identifier:'<-22.96345100,-43.19824200> radius 141.83',
center:<-22.96345100,-43.19824200>, radius:141.83m)
If you need a mailing string from your placemark you can get its postalAddress and create a string from it using CNPostalAddressFormatter string(for:) method:
import Contacts
extension CNPostalAddress {
var mailingAddress: String {
return CNPostalAddressFormatter.string(from: self, style: .mailingAddress)
}
}
DispatchQueue.main.async {
// UI update here
print(placemark.postalAddress?.mailingAddress ?? "") // "Rua Casuarina, 443\nLagoa\nRio de Janeiro RJ\n22011-040\nBrazil"
}
Rua Casuarina, 443 Lagoa Rio de Janeiro RJ 22011-040 Brazil

can someone explain why i can't return a value from this method?

i'm trying to use swift geocoding to get the city, but somehow the city only showup nested inside the method and when returned the variable is empty, here is the code i'm using.
class {
var locationManager = CLLocationManager()
var longitude = CLLocationDegrees()
var latitude = CLLocationDegrees()
var city = ""
override func viewDidLoad() {
super.viewDidLoad()
setupLocation()
var x = getLocation()
print("\n\n x your city is: \(x)\n\n"); // 'x' is always empty
if x == "paris" {
print("\n\n your city is: \(x)\n\n"); // 'x' is always empty
}
}
func getLocation() -> String {
longitude = (locationManager.location?.coordinate.longitude)!
latitude = (locationManager.location?.coordinate.latitude)!
let location = CLLocation(latitude: latitude, longitude: longitude)
print(location)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
print(location)
if error != nil {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks!.count > 0 {
let pm = placemarks![0]
print("locality is \(pm.locality)")
self.city = pm.locality!
print(" city first \(self.city)") //contains a city
}
else {
print("Problem with the data received from geocoder")
}
})
print("city second \(city)") //empty every time
return city
}
}
As pointed out here, you have to add a completion handler to your method:
func getLocation(completion: #escaping (String) -> Void) {
longitude = (locationManager.location?.coordinate.longitude)!
latitude = (locationManager.location?.coordinate.latitude)!
let location = CLLocation(latitude: latitude, longitude: longitude)
print(location)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
print(location)
if error != nil {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks!.count > 0 {
let pm = placemarks![0]
print("locality is \(pm.locality)")
completion(pm.locality!)
}
else {
print("Problem with the data received from geocoder")
}
})
}
And then just do:
getLocation() {
locality in
self.city = locality
}
You have stumbled upon a time issue. reverseGeocodeLocation is asynchronous, so the and the method returns before the closure is fully evaluated.
If you set breakpoints you would see that the
print("city second \(city)") //empty every time
line would trigger before the
print(" city first \(self.city)") //contains a city
one
Problem:
reverseGeocodeLocation is an asynchronous method (it doesn't evaluate immediately and would take time to evaluate). Before reverseGeocodeLocation is completed getLocation will be completed.
Solution:
Modify the getLocation to accept a closure as a parameter. Inside the completion handler of reverseGeocodeLocation call that closure and pass that value of the city

How to show the current state you are located in when using reverseGeocodeLocation in swift

I'm testing out reverseGeocodeLocation with this app that shows your closest address. I've gotten everything to work except for the showing of the current state that you are in (IL, NY, ext.). How do I do that? This is my current code:
CLGeocoder().reverseGeocodeLocation(userLocation)
{ (placemarks, error) -> Void in
if error != nil
{
println(error)
}
else
{
let pm = CLPlacemark(placemark: placemarks![0] as CLPlacemark)
var subThoroughtare:String = ""
var thoroughfare:String = ""
var subLocality:String = ""
var subAdministrativeArea:String = ""
var postalCode:String = ""
var country:String = ""
if pm.subThoroughfare != nil {subThoroughtare = pm.subThoroughfare!}
if pm.thoroughfare != nil {thoroughfare = pm.thoroughfare!}
if pm.subLocality != nil {subLocality = pm.subLocality!}
if pm.subAdministrativeArea != nil {subAdministrativeArea = pm.subAdministrativeArea!}
if pm.postalCode != nil {postalCode = pm.postalCode!}
if pm.country != nil {country = pm.country!}
self.addressLabel.text = "\(subThoroughtare) \(thoroughfare) \n \(subLocality) \n \(postalCode) \n \(country)"
}
}
and the output is this (example location):
County Road 1760
79529
United States
for the state you want to look at the administrativeArea
let state = pm.administrativeArea;
If you look at the definition for the CLPlacemark class it shows..
var administrativeArea: String! { get } // state, eg. CA

Resources