Closure for displaying results of reverseGeocoder in Swift - ios

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

Related

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!!

Find city name and country from latitude and longitude in Swift

I'm working on application in Swift3
and I have letter problem i can't find the answer for it.
How can I know city name and country short names base on latitude and longitude?
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate{
let locationManager = CLLocationManager()
var latitude: Double = 0
var longitude: Double = 0
override func viewDidLoad() {
super.viewDidLoad()
// For use when the app is open & in the background
locationManager.requestAlwaysAuthorization()
// For use when the app is open
//locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
locationManager.startUpdatingLocation()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
print(location.coordinate)
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if (status == CLAuthorizationStatus.denied){
showLocationDisabledpopUp()
}
}
func showLocationDisabledpopUp() {
let alertController = UIAlertController(title: "Background Location Access Disabled", message: "We need your location", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
let openAction = UIAlertAction(title: "Open Setting", style: .default) { (action) in
if let url = URL(string: UIApplicationOpenSettingsURLString){
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
alertController.addAction(openAction)
self.present(alertController, animated: true, completion: nil)
}
}
You can use CLGeocoder reverseGeocodeLocation method to fetch a CLPlacemark and get its country and locality properties info. Note that it is an asynchronous method so you will need to add a completion handler to your method when fetching that info:
import UIKit
import MapKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
extension CLLocation {
func fetchCityAndCountry(completion: #escaping (_ city: String?, _ country: String?, _ error: Error?) -> ()) {
CLGeocoder().reverseGeocodeLocation(self) { completion($0?.first?.locality, $0?.first?.country, $1) }
}
}
Usage
let location = CLLocation(latitude: -22.963451, longitude: -43.198242)
location.fetchCityAndCountry { city, country, error in
guard let city = city, let country = country, error == nil else { return }
print(city + ", " + country) // Rio de Janeiro, Brazil
}
edit/update:
iOS 11 or later CLPlacemark has a postalAddress property. You can import Contacts framework and use CNPostalAddressFormatter's string(from:) method to get a localized formatted address. You can also extend CLPlacemark and add some computed properties to better describe some of its properties:
import MapKit
import Contacts
extension CLPlacemark {
/// street name, eg. Infinite Loop
var streetName: String? { thoroughfare }
/// // eg. 1
var streetNumber: String? { subThoroughfare }
/// city, eg. Cupertino
var city: String? { locality }
/// neighborhood, common name, eg. Mission District
var neighborhood: String? { subLocality }
/// state, eg. CA
var state: String? { administrativeArea }
/// county, eg. Santa Clara
var county: String? { subAdministrativeArea }
/// zip code, eg. 95014
var zipCode: String? { postalCode }
/// postal address formatted
#available(iOS 11.0, *)
var postalAddressFormatted: String? {
guard let postalAddress = postalAddress else { return nil }
return CNPostalAddressFormatter().string(from: postalAddress)
}
}
extension CLLocation {
func placemark(completion: #escaping (_ placemark: CLPlacemark?, _ error: Error?) -> ()) {
CLGeocoder().reverseGeocodeLocation(self) { completion($0?.first, $1) }
}
}
Usage:
let location = CLLocation(latitude: 37.331676, longitude: -122.030189)
location.placemark { placemark, error in
guard let placemark = placemark else {
print("Error:", error ?? "nil")
return
}
print(placemark.postalAddressFormatted ?? "")
}
This will print
1 Infinite Loop
Cupertino CA 95014
United States
I would recommend integrating Google Maps API with your project. If you do, your task can be achieved using Reverse Geocoding Google provides.
Furthermore, Google there is Google Maps SDK for IOS development, which is also worth considering.
UPD: You can do that without integrating maps into your project. Basing on this answer, you can achieve that using http requests to Google API. The request to:
https://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&key=API_KEY
would return JSON object with information about the requested place, including country and city name.
BTW, I highly recommend using Alamofire to make http requests in Swift.
What you need is called reverse geocoding. As you have already declared some properties at the top. You need to add the CLGeocoder & CLPlancemark
let locationManager = CLLocationManager()
var location: CLLocation?
let geocoder = CLGeocoder()
var placemark: CLPlacemark?
// here I am declaring the iVars for city and country to access them later
var city: String?
var country: String?
var countryShortName: String?
Create a function where you can start the location services
func startLocationManager() {
// always good habit to check if locationServicesEnabled
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
also create another to stop once you're done with location geocoding
func stopLocationManager() {
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
}
in view didLoad or from anywhere you want to start the location manager add a check first
override func viewDidLoad() {
super.viewDidLoad()
let authStatus = CLLocationManager.authorizationStatus()
if authStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
if authStatus == .denied || authStatus == .restricted {
// add any alert or inform the user to to enable location services
}
// here you can call the start location function
startLocationManager()
}
implement the delegate methods for location manager didFailedWithError
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// print the error to see what went wrong
print("didFailwithError\(error)")
// stop location manager if failed
stopLocationManager()
}
implement the delegate method for location manager didUpdateLocations
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// if you need to get latest data you can get locations.last to check it if the device has been moved
let latestLocation = locations.last!
// here check if no need to continue just return still in the same place
if latestLocation.horizontalAccuracy < 0 {
return
}
// if it location is nil or it has been moved
if location == nil || location!.horizontalAccuracy > lastLocation.horizontalAccuracy {
location = lastLocation
// stop location manager
stopLocationManager()
// Here is the place you want to start reverseGeocoding
geocoder.reverseGeocodeLocation(lastLocation, completionHandler: { (placemarks, error) in
// always good to check if no error
// also we have to unwrap the placemark because it's optional
// I have done all in a single if but you check them separately
if error == nil, let placemark = placemarks, !placemark.isEmpty {
self.placemark = placemark.last
}
// a new function where you start to parse placemarks to get the information you need
self.parsePlacemarks()
})
}
}
Add the parsePlacemarks function
parsePlacemarks() {
// here we check if location manager is not nil using a _ wild card
if let _ = location {
// unwrap the placemark
if let placemark = placemark {
// wow now you can get the city name. remember that apple refers to city name as locality not city
// again we have to unwrap the locality remember optionalllls also some times there is no text so we check that it should not be empty
if let city = placemark.locality, !city.isEmpty {
// here you have the city name
// assign city name to our iVar
self.city = city
}
// the same story optionalllls also they are not empty
if let country = placemark.country, !country.isEmpty {
self.country = country
}
// get the country short name which is called isoCountryCode
if let countryShortName = placemark.isoCountryCode, !countryShortName.isEmpty {
self.countryShortName = countryShortName
}
}
} else {
// add some more check's if for some reason location manager is nil
}
}
You have to cmd+click on CLPlacemark to see all the properties that you can access for example street name is called thoroughfare & the number is is called subThoroughfare continue reading the documentation for more information
Note: You have to check for locations error also geocoder error which I haven't implemented here but you have to take care of those errors and the best place to check error codes and everything else is apples documentation
Update: Check paresPlacemarks function where I added isoCountryCode which is equal to country shortName No need to add extra network calls to google API and Alamofire while your already using location services
Here is the Swift 4 code:
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges()
// Here you can check whether you have allowed the permission or not.
if CLLocationManager.locationServicesEnabled()
{
switch(CLLocationManager.authorizationStatus())
{
case .authorizedAlways, .authorizedWhenInUse:
print("Authorize.")
let latitude: CLLocationDegrees = (locationManager.location?.coordinate.latitude)!
let longitude: CLLocationDegrees = (locationManager.location?.coordinate.longitude)!
let location = CLLocation(latitude: latitude, longitude: longitude) //changed!!!
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if error != nil {
return
}else if let country = placemarks?.first?.country,
let city = placemarks?.first?.locality {
print(country)
self.cityNameStr = city
}
else {
}
})
break
case .notDetermined:
print("Not determined.")
self.showAlertMessage(messageTitle: "Bolo Board", withMessage: "Location service is disabled!!")
break
case .restricted:
print("Restricted.")
self.showAlertMessage(messageTitle: "Bolo Board", withMessage: "Location service is disabled!!")
break
case .denied:
print("Denied.")
}
}
}
func showAlertMessage(messageTitle: NSString, withMessage: NSString) ->Void {
let alertController = UIAlertController(title: messageTitle as String, message: withMessage as String, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action:UIAlertAction!) in
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "Settings", style: .default) { (action:UIAlertAction!) in
if let url = URL(string: "App-Prefs:root=Privacy&path=LOCATION/com.company.AppName") {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
// Fallback on earlier versions
}
}
}
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion:nil)
}
You can use CLGeocoder, from CoreLocation, for that. From Apple documentation (emphasizes mine):
A single-shot object for converting between geographic coordinates and place names.
The CLGeocoder class provides services for converting between a coordinate (specified as a latitude and longitude) and the user-friendly representation of that coordinate. A user-friendly representation of the coordinate typically consists of the street, city, state, and country information corresponding to the given location...
This service is unrelated to MapKit and, as such, don't require you use/show a map in your app at all.
import Foundation
import CoreLocation
let location = CLLocation(latitude: 37.3321, longitude: -122.0318)
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
guard let placemark = placemarks?.first else {
let errorString = error?.localizedDescription ?? "Unexpected Error"
print("Unable to reverse geocode the given location. Error: \(errorString)")
return
}
let reversedGeoLocation = ReversedGeoLocation(with: placemark)
print(reversedGeoLocation.formattedAddress)
// Apple Inc.,
// 1 Infinite Loop,
// Cupertino, CA 95014
// United States
}
struct ReversedGeoLocation {
let name: String // eg. Apple Inc.
let streetName: String // eg. Infinite Loop
let streetNumber: String // eg. 1
let city: String // eg. Cupertino
let state: String // eg. CA
let zipCode: String // eg. 95014
let country: String // eg. United States
let isoCountryCode: String // eg. US
var formattedAddress: String {
return """
\(name),
\(streetNumber) \(streetName),
\(city), \(state) \(zipCode)
\(country)
"""
}
// Handle optionals as needed
init(with placemark: CLPlacemark) {
self.name = placemark.name ?? ""
self.streetName = placemark.thoroughfare ?? ""
self.streetNumber = placemark.subThoroughfare ?? ""
self.city = placemark.locality ?? ""
self.state = placemark.administrativeArea ?? ""
self.zipCode = placemark.postalCode ?? ""
self.country = placemark.country ?? ""
self.isoCountryCode = placemark.isoCountryCode ?? ""
}
}
1 . import CoreLocation
2 . insert CLLocationManagerDelegate in your class
3 . Do the delegate methods described below... hope it will help you
you can find city name and country through following these steps...Here is my code
import UIKit
import CoreLocation
class MyViewController:UIViewController,CLLocationManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if( CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == .authorizedAlways){
if let currentLocation = locationManager.location
{
if NetworkFunctions.NetworkRechability()
{
getAddressFromLatLon(pdblLatitude: "\(Double((currentLocation.coordinate.latitude)))", withLongitude: "\(Double((currentLocation.coordinate.longitude)))")
}
}
}
}
func getAddressFromLatLon(pdblLatitude: String, withLongitude pdblLongitude: String) {
var center : CLLocationCoordinate2D = CLLocationCoordinate2D()
let lat: Double = Double("\(pdblLatitude)")!
let lon: Double = Double("\(pdblLongitude)")!
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)
{
}
if placemarks != nil
{
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! + ", "
//uuuuu
if(location_city != pm.locality!.trimmingCharacters(in: .whitespaces))
{
location_city=pm.locality!.trimmingCharacters(in: .whitespaces)
DispatchQueue.main.async{
self.GetBeeWatherDetails(district: pm.locality!, country: pm.country!)
}
}
}
}
if pm.postalCode != nil {
addressString = addressString + pm.postalCode! + " "
}
}
}
})
}
}
Add this extension in your swift file.
extension CLLocation {
func fetchAddress(completion: #escaping (_ address: String?, _ error: Error?) -> ()) {
CLGeocoder().reverseGeocodeLocation(self) {
let palcemark = $0?.first
var address = ""
if let subThoroughfare = palcemark?.subThoroughfare {
address = address + subThoroughfare + ","
}
if let thoroughfare = palcemark?.thoroughfare {
address = address + thoroughfare + ","
}
if let locality = palcemark?.locality {
address = address + locality + ","
}
if let subLocality = palcemark?.subLocality {
address = address + subLocality + ","
}
if let administrativeArea = palcemark?.administrativeArea {
address = address + administrativeArea + ","
}
if let postalCode = palcemark?.postalCode {
address = address + postalCode + ","
}
if let country = palcemark?.country {
address = address + country + ","
}
if address.last == "," {
address = String(address.dropLast())
}
completion(address,$1)
// completion("\($0?.first?.subThoroughfare ?? ""), \($0?.first?.thoroughfare ?? ""), \($0?.first?.locality ?? ""), \($0?.first?.subLocality ?? ""), \($0?.first?.administrativeArea ?? ""), \($0?.first?.postalCode ?? ""), \($0?.first?.country ?? "")",$1)
}
}
}
And then call it on any of the CLLocation object.
Eg:
(myLocation as? CLLocation)!.fetchAddress { (address, error) in
guard let address = address, error == nil else
{return }
I had also the same issue .You can use this code.
func placePicker(_ viewController: GMSPlacePickerViewController, didPick place: GMSPlace) {
viewController.dismiss(animated: true, completion: nil)
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// Address dictionary
print(placeMark.addressDictionary as Any)
//
print("Place name \(place.name)")
print("Place address \(String(describing: place.formattedAddress))")
print("Place attributions \(String(describing: place.attributions))")
})
}
Hope this will resolve your problem.
This method will give you the current location, city name ,country name etc.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
// Process Response
if let error = error {
print("Unable to Reverse Geocode Location (\(error))")
} else {
if let placemarks = placemarks, let placemark = placemarks.first {
self.city = placemark.locality!
//self.country = placemark.country!
}
}
}
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
zoom: zoomLevel)
self.locationv = CLLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
if myView.isHidden {
myView.isHidden = false
myView.camera = camera
} else {
myView.animate(to: camera)
}
}
See my answer in swift 4.1 Xcode 9.4.1. You can get even village name details also. Get location name from Latitude & Longitude in iOS

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

iOS - How can I return latitude and longitude?

I can't return latitude and longitude. I always get a 0.0 and 0.0.
What can I do to get these values?
Code :
func forwardGeocoding (address: String) -> (Double, Double) {
let geoCoder = CLGeocoder()
var latitude: Double = 0.0
var longitude: Double = 0.0
geoCoder.geocodeAddressString(address) { (placemarks: [CLPlacemark]?, error: NSError?) -> Void in
if error != nil {
print(error?.localizedDescription)
} else {
if placemarks!.count > 0 {
let placemark = placemarks![0] as CLPlacemark
let location = placemark.location
latitude = Double((location?.coordinate.latitude)!)
longitude = Double((location?.coordinate.longitude)!)
print("before : \(latitude, longitude)")
}
}
}
print("after : \(latitude, longitude)")
return (latitude, longitude)
}
This my viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
forwardGeocoding("New York, NY, United States")
}
Result:
after : (0.0, 0.0)
before : (40.713054, -74.007228)
The problem with your code is that geocoding requests are asynchronous, so the return statement is executed before the geocoding results are actually retrieved.
I'd probably use one of two options to fix this. First, instead of returning a tuple, make your own completion handler, and call it after the placemark is found:
func forwardGeocoding (address: String, completion: (CLLocationCoordinate2D) -> Void) {
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { (placemarks: [CLPlacemark]?, error: NSError?) -> Void in
if error != nil {
print(error?.localizedDescription)
} else {
if placemarks!.count > 0 {
let placemark = placemarks![0] as CLPlacemark
let location = placemark.location
completion(location.coordinate)
}
}
}
}
Now when you call this function you can provide the completion with the relevant values from wherever you're calling the function.
If the function is actually a method in a class and it never needs to be called from another class, you could have it set properties of the class, and those properties could have didSet blocks. For example:
class SomeClass {
var coordinates: CLLocationCoordinate2D {
didSet {
doSomethingWithCoordinates()
}
}
private func forwardGeocoding (address: String, completion: (CLLocationCoordinate2D) -> Void) {
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { (placemarks: [CLPlacemark]?, error: NSError?) -> Void in
if error != nil {
print(error?.localizedDescription)
} else {
if placemarks!.count > 0 {
let placemark = placemarks![0] as CLPlacemark
let location = placemark.location
self.coordinates = location.coordinate
}
}
}
}
}
The first options is probably more versatile, but the second avoids having completion blocks withing completion blocks, which can sometimes become confusing to keep track of in your code.
It's quite obvious that your function returns always (0.0, 0.0). It's because geoCoder.geocodeAddressString() returns placemarks asynchronously.
It's time-consuming operation so you have to modify your code to handle that.
One of possible solutions is to modify your function:
func forwardGeocoding (address: String, completion: (Bool, CLLocationCoordinate2D!) -> () ) {
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { (placemarks: [CLPlacemark]?, error: NSError?) -> Void in
if error != nil {
print(error?.localizedDescription)
completion(false,nil)
} else {
if placemarks!.count > 0 {
let placemark = placemarks![0] as CLPlacemark
let location = placemark.location
completion(true, location?.coordinate)
}
}
}
And call it:
self.forwardGeocoding(YourAdress, completion {
success, coordinate in
if success {
let lat = coordinate.lattitude
let long = coordinate.longitude
// Do sth with your coordinates
} else {
// error sth went wrong
}
}
Notice that your function returns nothing.
Hope that helps.

Swift - CLGeocoder reverseGeocodeLocation completionHandler closure

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
}

Resources