Find city name and country from latitude and longitude in Swift Find city name and country from latitude and longitude in Swift ios ios

Find city name and country from latitude and longitude in Swift


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 UIKitimport MapKitimport PlaygroundSupportPlaygroundPage.current.needsIndefiniteExecution = trueextension 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 MapKitimport Contactsextension 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 latervar 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