Find city name and country from latitude and longitude in Swift

前端 未结 11 1942
既然无缘
既然无缘 2020-12-12 22:58

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 latitud

相关标签:
11条回答
  • 2020-12-12 23:25

    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


    0 讨论(0)
  • 2020-12-12 23:30

    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

    0 讨论(0)
  • 2020-12-12 23:31
    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 ?? ""
        }
    }
    
    0 讨论(0)
  • 2020-12-12 23:34

    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)
    }
    
    0 讨论(0)
  • 2020-12-12 23:35

    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.

    0 讨论(0)
提交回复
热议问题