【问题标题】:Find city name and country from latitude and longitude in Swift在 Swift 中根据经纬度查找城市名称和国家/地区
【发布时间】:2017-10-17 07:03:39
【问题描述】:

我正在开发 Swift3 中的应用程序 我有字母问题,我找不到答案。

如何根据经纬度知道城市名称和国家简称?

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)
    }
}

【问题讨论】:

标签: ios swift location core-location latitude-longitude


【解决方案1】:

您可以使用 CLGeocoder reverseGeocodeLocation 方法获取 CLPlacemark 并获取其 countrylocality 属性信息。请注意,它是一个异步方法,因此您需要在获取该信息时向您的方法添加一个完成处理程序:

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) }
    }
}

用法

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
}

编辑/更新:

iOS 11 或更高版本 CLPlacemark 具有 postalAddress 属性。您可以import Contacts 框架并使用CNPostalAddressFormatterstring(from:) 方法来获取本地化的格式化地址。您还可以扩展 CLPlacemark 并添加一些计算属性以更好地描述它的一些属性:

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) }
    }
}

用法:

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 ?? "")
}

这将打印出来

1 无限循环
库比蒂诺 CA 95014
美国


【讨论】:

  • 谢谢你这很有用,但我怎样才能找到国家的简称
  • developer.apple.com/reference/corelocation/clplacemark/… 你可以使用 isocountrycode 代替国家
  • @LeoDabus,我也想知道您是否可以帮助我找出与此相关的其他内容。假设我正在使用您的fetchCityAndCountryCLLogationManager 来获取用户当前的latitudelongitude。将 let location = CLLocation(latitude: -22.963451, longitude: -43.198242) 替换为 let latitude = locationManager.location?.coordinate.latitudelet longitude = locationManager.location?.coordinate.longitude 然后从那里创建一个 CLLocation 对象将不起作用,因为 locationManager 在 fetch 执行时没有足够的时间来获取位置
  • 您需要在 locationManager didUpdateLocations 方法中执行此操作
  • @LeoDabus 我想得到所有的县,如果我选择印度国家,那么它会显示所有的州和城市吗?你能帮我吗..谢谢
【解决方案2】:

我建议将Google Maps API 与您的项目集成。如果你这样做了,你的任务可以使用谷歌提供的Reverse Geocoding来完成。

另外,谷歌还有Google Maps SDK做IOS开发,也值得考虑。

UPD:您可以在不将地图集成到您的项目中的情况下做到这一点。根据this 的回答,您可以使用对 Google API 的 http 请求来实现。请求:

https://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&key=API_KEY 

将返回 JSON 对象,其中包含有关所请求地点的信息,包括国家和城市名称。

顺便说一句,我强烈建议使用Alamofire 在 Swift 中发出 http 请求。

【讨论】:

  • 我没有在我的项目中插入地图,我只是将此代码用于纬度和经度
  • @A.Ali,基于this的回答,你可以轻松使用Google API,无需集成地图,只需使用Alamofire触发http请求
  • 如果您已经在使用 gps,则您不必包含其他 API 和网络请求,如果他需要国家/地区短名称是 isoCountryCode,您应该深入了解不需要的 Apples 文档为此进行网络调用
  • Alamofire 确实很适合在 Swift 中发出 HTTP 请求;)
  • 建议使用谷歌可能不是最好的答案(除非你对应用有一些好的投资)。在您仔细阅读 T&C 之前,Google API 会给您一种免费的错觉。也有使用限制,因此请注意如何使用它。如果您只是想学习,那么一定要探索 Google API,但我建议首先学习 Apple 的 CoreLocation,因为它几乎可以完成 Google 所做的所有事情(除了一些高级功能),但需要更多编码(这很有趣) 并且如果您有效地编码它是免费的。在下面推荐@LeoDabus 答案。
【解决方案3】:

您需要的是所谓的反向地理编码。因为您已经在顶部声明了一些属性。您需要添加 CLGeocoderCLPlancemark

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?

创建一个可以启动定位服务的函数

func startLocationManager() {
    // always good habit to check if locationServicesEnabled
    if CLLocationManager.locationServicesEnabled() {
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.startUpdatingLocation()
    }
}

完成位置地理编码后,还可以创建另一个停止

func stopLocationManager() {
   locationManager.stopUpdatingLocation()
   locationManager.delegate = nil
}

在查看 didLoad 或从您想要启动位置管理器的任何位置首先添加检查

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()

}

实现位置管理器 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()
}

实现位置管理器 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()

           })
    }
}

添加 parsePlacemarks 函数

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
    }

}

您必须 cmd+单击 CLPlacemark 才能查看您可以访问的所有属性,例如街道名称称为 thoroughfare 和编号称为 subThoroughfare 继续阅读更多信息的文档

注意:您必须检查位置错误以及地理编码器错误,我没有在此处实现,但您必须处理这些错误以及检查错误代码的最佳位置,其他一切都是苹果文档

更新:检查 paresPlacemarks 函数,我在其中添加了 isoCountryCode,它等于国家/地区 shortName 当您已经使用位置服务时,无需向 google API 和 Alamofire 添加额外的网络调用

【讨论】:

    【解决方案4】:

    这是 Swift 4 代码:

      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)
    }
    

    【讨论】:

    • app-prefs:root=privacy&path=location/com.company.appname 因为这条线苹果拒绝了应用程序
    【解决方案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 ?? ""
        }
    }
    

    【讨论】:

      【解决方案6】:

      为此,您可以使用来自 CoreLocation 的 CLGeocoder。来自 Apple 文档(强调我的):

      用于在地理坐标和地名之间转换的单次对象。

      CLGeocoder 类提供在坐标(指定为纬度和经度)和该坐标的用户友好表示之间进行转换的服务。坐标的用户友好表示通常包括街道、城市、州和与给定位置相对应的国家信息...

      此服务与MapKit 无关,因此根本不需要您在应用中使用/显示地图。

      【讨论】:

      • MKReverseGeocoder 多年前已被弃用。你应该使用CLGeocoder
      • 我没有在我的项目中插入地图,我只是将此代码用于纬度和经度,我需要国家名称来链接我将使用它
      【解决方案7】:

      1 .导入核心位置 2.在您的班级中插入 CLLocationManagerDelegate 3.执行下面描述的委托方法...希望对您有所帮助 您可以通过以下步骤找到城市名称和国家...这是我的代码

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

      【讨论】:

        【解决方案8】:

        在你的 swift 文件中添加这个扩展。

        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)
            }
        }
        

        }

        然后在任何 CLLocation 对象上调用它。

        例如:

         (myLocation as? CLLocation)!.fetchAddress { (address, error) in
                                guard let address = address, error == nil else                              
        {return }
        

        【讨论】:

          【解决方案9】:

          我也有同样的问题。你可以使用这个代码。

          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))")
          
          
          
          })
          }
          

          希望这能解决您的问题。

          【讨论】:

            【解决方案10】:

            此方法将为您提供当前位置、城市名称、国家名称等。

            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)
                }
            }
            

            【讨论】:

              【解决方案11】:

              在 swift 4.1 Xcode 9.4.1 中查看我的答案。您甚至可以获得村庄名称的详细信息。 Get location name from Latitude & Longitude in iOS

              【讨论】:

                猜你喜欢
                • 2014-06-01
                • 1970-01-01
                • 1970-01-01
                • 2012-05-22
                • 2019-10-27
                • 2012-12-04
                • 1970-01-01
                • 2020-04-16
                • 1970-01-01
                相关资源
                最近更新 更多