40

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)
    }
}
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
joshua
  • 947
  • 2
  • 8
  • 14
  • https://developer.apple.com/reference/corelocation/clgeocoder – dan May 17 '17 at 17:31
  • See my answer in swift 4.1 Xcode 9.4.1. You can get even village name details also. https://stackoverflow.com/questions/16647996/get-location-name-from-latitude-longitude-in-ios/51797299#51797299 – Naresh Aug 11 '18 at 07:03
  • https://medium.com/@tunvirrahmantusher/get-location-from-latitude-and-longitude-postman-46245fa35cad – Tunvir Rahman Tusher Dec 29 '19 at 12:02

11 Answers11

68

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


Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 1
    thank you this very useful but how can i find country short names – joshua May 17 '17 at 18:37
  • 1
    https://developer.apple.com/reference/corelocation/clplacemark/1423796-isocountrycode You can use isocountrycode instead of country – Leo Dabus May 17 '17 at 18:40
  • @LeoDabus, I would also like to know if you could help me figure out something else related to this. Say I am using your `fetchCityAndCountry` and a `CLLogationManager` to grab the user's current `latitude` and `longitude`. Replacing `let location = CLLocation(latitude: -22.963451, longitude: -43.198242)` with `let latitude = locationManager.location?.coordinate.latitude` and `let longitude = locationManager.location?.coordinate.longitude` and then creating a `CLLocation` object from there will not work because the locationManager hasn't had enough time to grab the location when fetch executes – Swifty May 18 '19 at 02:12
  • You need to do that inside locationManager didUpdateLocations method – Leo Dabus May 18 '19 at 02:30
  • @LeoDabus i want to get all the counties and also if i select india country then it will shows all the states and cities of that? can you please help me on that.. Thanks – Yogesh Patel Feb 03 '20 at 09:47
18

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.

Community
  • 1
  • 1
pomo_mondreganto
  • 2,028
  • 2
  • 28
  • 56
  • i dont insert map in my project i just use this code for latitude and longitude – joshua May 17 '17 at 17:35
  • @A.Ali, based on [this](http://stackoverflow.com/a/32758343/4511620) answer, you can use `Google API` easily without integrating maps, just using `Alamofire` to fire http request – pomo_mondreganto May 17 '17 at 17:43
  • You don't have to include other API's and network requests if your already using gps if he needs country short name is **isoCountryCode** you should look some deeper in Apples documentation not need to make network calls for that – Khalid Afridi May 17 '17 at 19:19
  • Alamofire is indeed nice to make HTTP requests in Swift ;) – Paulo Mattos May 17 '17 at 19:56
  • 13
    Suggesting the use of Google is probably not the best answer (unless you have some good investment for the app) . The Google APIs gives you the illusion of being free until you read the T&C carefully. There are usage restrictions as well so be careful with how you use it. If you are just trying to learn then by all means explore Google API, but I recommend learning Apple's `CoreLocation` first since it can do pretty much everything Google does (except a few advanced features) but with a little more coding (which is fun) and is free if you code it efficiently. Recommend @LeoDabus answer below. – Anjan Biswas Oct 27 '17 at 18:59
  • Google place api is paid now a days – Vivek Gajbe May 29 '20 at 05:05
  • The returned json here will not give you an explicit 'city' or 'country' key. If trying to use worldwide this json is pretty useless in my experience as the address components 'long_name' etc. are non-standard country to country, my $0.02... – GordonW Sep 27 '20 at 21:34
11

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

Khalid Afridi
  • 913
  • 5
  • 12
8

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)
}
Mannam Brahmam
  • 2,225
  • 2
  • 24
  • 36
  • app-prefs:root=privacy&path=location/com.company.appname because of this line apple rejected the app – Disha Jan 16 '19 at 07:29
5

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.

Community
  • 1
  • 1
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
  • `MKReverseGeocoder` was deprecated years ago. You should use a `CLGeocoder`. – dan May 17 '17 at 17:33
  • i dont insert map in my project i just use this code for latitude and longitude and i need country name to link i will use it – joshua May 17 '17 at 17:35
5
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 ?? ""
    }
}
Dilip Mishra
  • 1,422
  • 13
  • 17
1

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! + " "
                        }

                    }
                }
        })

    }

}
Adrian P
  • 6,479
  • 4
  • 38
  • 55
Fansad PP
  • 459
  • 4
  • 11
1

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 }
bharathi kumar
  • 210
  • 2
  • 8
0

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.

Ayush Dixit
  • 467
  • 4
  • 10
0

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)
    }
}
Akbar Khan
  • 2,215
  • 19
  • 27
0

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

Naresh
  • 16,698
  • 6
  • 112
  • 113