【问题标题】:Swift - Generate an Address Format from Reverse GeocodingSwift - 从反向地理编码生成地址格式
【发布时间】:2016-12-28 08:49:06
【问题描述】:

我正在尝试在 Swift 3 中使用 CLGeocoder 生成一个格式化的完整地址。我参考了这个 SO 线程来获取下面给出的代码。

但是,有时应用程序崩溃并在该行出现“nil”错误:

//Address dictionary
print(placeMark.addressDictionary ?? "")

问题:

  1. 如何连接从 GeoCoder 检索到的这些值以形成完整地址? (街道 + 城市 + 等)
  2. 当 func 无法找到地址时,我如何处理 nil 错误?

完整代码:

func getAddress() -> String {
        var address: String = ""

        let geoCoder = CLGeocoder()
        let location = CLLocation(latitude: selectedLat, longitude: selectedLon)
        //selectedLat and selectedLon are double values set by the app in a previous process

        geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in

            // Place details
            var placeMark: CLPlacemark!
            placeMark = placemarks?[0]

            // Address dictionary
            //print(placeMark.addressDictionary ?? "")

            // Location name
            if let locationName = placeMark.addressDictionary!["Name"] as? NSString {
                //print(locationName)
            }

            // Street address
            if let street = placeMark.addressDictionary!["Thoroughfare"] as? NSString {
                //print(street)
            }

            // City
            if let city = placeMark.addressDictionary!["City"] as? NSString {
                //print(city)
            }

            // Zip code
            if let zip = placeMark.addressDictionary!["ZIP"] as? NSString {
                //print(zip)
            }

            // Country
            if let country = placeMark.addressDictionary!["Country"] as? NSString {
                //print(country)
            }

        })

        return address;
    } 

【问题讨论】:

  • 在 swift 4.1 Xcode 9.4.1 中查看我的答案。您甚至可以获得村庄名称的详细信息。 stackoverflow.com/questions/16647996/…
  • addressDictionary 在 iOS 11 及更高版本中已弃用

标签: ios swift xcode google-maps reverse-geocoding


【解决方案1】:
func getAddressFromLatLon(pdblLatitude: String, withLongitude pdblLongitude: String) {
        var center : CLLocationCoordinate2D = CLLocationCoordinate2D()
        let lat: Double = Double("\(pdblLatitude)")!
        //21.228124
        let lon: Double = Double("\(pdblLongitude)")!
        //72.833770
        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)
                {
                    print("reverse geodcode fail: \(error!.localizedDescription)")
                }
                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! + ", "
                    }
                    if pm.postalCode != nil {
                        addressString = addressString + pm.postalCode! + " "
                    }


                    print(addressString)
              }
        })

    }

【讨论】:

  • 和最后一个打印地址字符串。你有你的完整地址
  • 这有问题。 func 在等待从 LatLng 处理地址之前会经历这个过程。所以我最终得到一个空字符串
  • @RickGrimesLikesWalkerSoup 检查我在项目中使用的整个方法。你也可以使用它
  • 你是一个美丽的人。这就像一个魅力。谢谢老兄!
  • @HimanshuMoradiya:工作就像一个魅力,非常感谢,但要提一提的是,你不应该强行打开 placemark,因为它有时可能是 nil 发生在我身上, 使用 if let 来消除不必要的崩溃。 :P 干杯。快乐编码。
【解决方案2】:

格式化地址很困难,因为每个国家/地区都有自己的格式。

只需几行代码,您就可以获得每个国家/地区的正确地址格式,并让 Apple 处理差异。

从iOS 11开始,可以获取Contacts框架地址:

extension CLPlacemark {
    @available(iOS 11.0, *)
    open var postalAddress: CNPostalAddress? { get }
}

此扩展是Contacts 框架的一部分。 这意味着,在您完成之前,此功能在 XCode 代码完成中对您不可见

import Contacts

通过这个额外的导入,你可以做类似的事情

CLGeocoder().reverseGeocodeLocation(location, preferredLocale: nil) { (clPlacemark: [CLPlacemark]?, error: Error?) in
    guard let place = clPlacemark?.first else {
        print("No placemark from Apple: \(String(describing: error))")
        return
    }

    let postalAddressFormatter = CNPostalAddressFormatter()
    postalAddressFormatter.style = .mailingAddress
    var addressString: String?
    if let postalAddress = place.postalAddress {
        addressString = postalAddressFormatter.string(from: postalAddress)
    }
}

并获取格式为地址中国家/地区格式的地址。

格式化程序甚至支持格式化为属性字符串。

在 iOS 11 之前,您可以自己将CLPlacemark 转换为CNPostalAddress,并且仍然可以使用CNPostalAddressFormatter 的国家/地区特定格式。

【讨论】:

  • 我喜欢这种方法,但是当我添加您的扩展程序时,我看到在它自己的类型中使用了“postalAddress”?
  • 不要添加扩展名。我正在展示 Apple 添加的扩展。只需将其删除,一切都会好起来的。
  • 哦哈哈,我的错。
  • 救命稻草。感谢您的帮助。
  • @GerdCastan 是否有类似的内置函数可以帮助将MKPointAnnotation 转换为CLPlacemark?到目前为止,我能看到的只是自己创建 addressDictionary....
【解决方案3】:

这是我的 swift 3 代码

func getAdressName(coords: CLLocation) {

    CLGeocoder().reverseGeocodeLocation(coords) { (placemark, error) in
            if error != nil {
                print("Hay un error")
            } else {

                let place = placemark! as [CLPlacemark]
                if place.count > 0 {
                    let place = placemark![0]
                    var adressString : String = ""
                    if place.thoroughfare != nil {
                        adressString = adressString + place.thoroughfare! + ", "
                    }
                    if place.subThoroughfare != nil {
                        adressString = adressString + place.subThoroughfare! + "\n"
                    }
                    if place.locality != nil {
                        adressString = adressString + place.locality! + " - "
                    }
                    if place.postalCode != nil {
                        adressString = adressString + place.postalCode! + "\n"
                    }
                    if place.subAdministrativeArea != nil {
                        adressString = adressString + place.subAdministrativeArea! + " - "
                    }
                    if place.country != nil {
                        adressString = adressString + place.country!
                    }

                    self.lblPlace.text = adressString
                }
            }
        }
  }

您可以轻松调用上述函数,例如:

let cityCoords = CLLocation(latitude: newLat, longitude: newLon)
cityData(coord: cityCoords)

【讨论】:

  • 让 place = 地标!因为 [CLPlacemark] 不强制解包它会在没有网络上崩溃整个应用程序
【解决方案4】:
  1. 为了解决空地址问题,您可以使用类属性来保存附加值,也可以使用闭包将值返回给调用函数
  2. 要修复崩溃,您需要避免强制解包选项

使用闭包你可以这样做:

// Using closure
func getAddress(handler: @escaping (String) -> Void)
{
    var address: String = ""
    let geoCoder = CLGeocoder()
    let location = CLLocation(latitude: selectedLat, longitude: selectedLon)
    //selectedLat and selectedLon are double values set by the app in a previous process
    
    geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
        
        // Place details
        var placeMark: CLPlacemark?
        placeMark = placemarks?[0]
        
        // Address dictionary
        //print(placeMark.addressDictionary ?? "")
        
        // Location name
        if let locationName = placeMark?.addressDictionary?["Name"] as? String {
            address += locationName + ", "
        }
        
        // Street address
        if let street = placeMark?.addressDictionary?["Thoroughfare"] as? String {
            address += street + ", "
        }
        
        // City
        if let city = placeMark?.addressDictionary?["City"] as? String {
            address += city + ", "
        }
        
        // Zip code
        if let zip = placeMark?.addressDictionary?["ZIP"] as? String {
            address += zip + ", "
        }
        
        // Country
        if let country = placeMark?.addressDictionary?["Country"] as? String {
            address += country
        }
        
       // Passing address back
       handler(address)
    })
}

你可以像这样调用方法:

getAddress { (address) in
    print(address)
}

【讨论】:

  • 在这一行给出错误。 geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in "Escaping closures capture non-escaping parameter 'handler'"
  • @Ashu 在以后的 swift 版本中,您必须为闭包编写 @escaping,更新了答案。请看一看。
【解决方案5】:

要连接,您可以简单地将 return address 替换为:

return "\(locationName), \(street), \(city), \(zip), \(country)"

【讨论】:

  • 这没有提供问题的答案。要批评或要求作者澄清,请在他们的帖子下方留下评论。 - From Review
  • @Mack 这个回答“如何连接...”
  • 可能你应该添加更多关于做什么、如何使用、在哪里使用这条线和一些东西的解释。现在只是简单地放置 1 行并不总是得到正确的解决方案。
【解决方案6】:

保持简单 - 一个完全兼容 Swift 3 和 4 的视图控制器示例,用于从用户的位置获取格式化的地址字符串(如果您想在字符串中获得更多信息,请添加 CLPlacemark 中可用的其他键):

import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {

let manager = CLLocationManager()
let geocoder = CLGeocoder()

var locality = ""
var administrativeArea = ""
var country = ""

override func viewDidLoad() {
    super.viewDidLoad()

    manager.delegate = self
    manager.desiredAccuracy = kCLLocationAccuracyBest
    manager.requestWhenInUseAuthorization()
    manager.startUpdatingLocation()

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location = locations[0]
        manager.stopUpdatingLocation()

    geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error) in
        if (error != nil) {
            print("Error in reverseGeocode")
            }

        let placemark = placemarks! as [CLPlacemark]
        if placemark.count > 0 {
            let placemark = placemarks![0]
            self.locality = placemark.locality!
            self.administrativeArea = placemark.administrativeArea!
            self.country = placemark.country!
        }
    })
}

func userLocationString() -> String {
    let userLocationString = "\(locality), \(administrativeArea), \(country)"
    return userLocationString
}

}

在本例中调用 print(userLocationString()) 将打印:郊区、州、国家

不要忘记事先将 Privacy - Location When In Use Usage Description 添加到您的 Info.plist 文件中,以允许用户授予您的应用使用位置服务的权限。

【讨论】:

  • 街道地址呢?
  • @Zonker.in.Geneva 查看解释中的链接,更具体地说是“thoroughfare”。在此示例中,您将使用 placemark.subThoroughfare、placemark.thoroughfare,可能还有 placemark.subLocality 和 placemark.postalCode developer.apple.com/documentation/corelocation/clplacemark/…
【解决方案7】:

这里有一个 2-3 行的答案版本:

    func getAddress(placemarks: [CLPlacemark]) -> String {
        guard let placemark = placemarks.first, !placemarks.isEmpty else {return ""}
        let outputString = [placemark.locality,
                            placemark.subLocality,
                            placemark.thoroughfare,
                            placemark.postalCode,
                            placemark.subThoroughfare,
                            placemark.country].compactMap{$0}.joined(separator: ", ")
        print(outputString)
        return outputString
    }

【讨论】:

    【解决方案8】:
    CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: vehicleLocation.latitude, longitude: vehicleLocation.latitude), completionHandler: {(placemarks, error) -> Void in
    
      guard error == nil else {completionHandler(nil); return}
    
      guard let place = placemarks else {completionHandler(nil); return}
    
      if place.count > 0 {
        let pm = place[0]
    
        var addArray:[String] = []
        if let name = pm.name {
          addArray.append(name)
        }
        if let thoroughfare = pm.thoroughfare {
          addArray.append(thoroughfare)
        }
        if let subLocality = pm.subLocality {
          addArray.append(subLocality)
        }
        if let locality = pm.locality {
          addArray.append(locality)
        }
        if let subAdministrativeArea = pm.subAdministrativeArea {
          addArray.append(subAdministrativeArea)
        }
        if let administrativeArea = pm.administrativeArea {
          addArray.append(administrativeArea)
        }
        if let country = pm.country {
          addArray.append(country)
        }
        if let postalCode = pm.postalCode {
          addArray.append(postalCode)
        }
    
        let addressString = addArray.joined(separator: ",\n")
    
        print(addressString)
    
        completionHandler(addressString)
      }
      else { completionHandler(nil)}
    })
    

    【讨论】:

      【解决方案9】:

      我为地理编码创建自己的静态类并获取 CLPlacemark 的属性并获得完整的地址,例如“通常”返回 Google:

      import Foundation
      import CoreLocation
      
      class ReverseGeocoding {
      
          static func geocode(latitude: Double, longitude: Double, completion: @escaping (CLPlacemark?, _ completeAddress: String?, Error?) -> ())  {
              CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude)) { placemarks, error in
                  guard let placemark = placemarks?.first, error == nil else {
                      completion(nil, nil, error)
                      return
                  }
      
                  let completeAddress = getCompleteAddress(placemarks)
      
                  completion(placemark, completeAddress, nil)
              }
          }
      
          static private func getCompleteAddress(_ placemarks: [CLPlacemark]?) -> String {
              guard let placemarks = placemarks else {
                  return ""
              }
      
              let place = placemarks as [CLPlacemark]
              if place.count > 0 {
                  let place = placemarks[0]
                  var addressString : String = ""
                  if place.thoroughfare != nil {
                      addressString = addressString + place.thoroughfare! + ", "
                  }
                  if place.subThoroughfare != nil {
                      addressString = addressString + place.subThoroughfare! + ", "
                  }
                  if place.locality != nil {
                      addressString = addressString + place.locality! + ", "
                  }
                  if place.postalCode != nil {
                      addressString = addressString + place.postalCode! + ", "
                  }
                  if place.subAdministrativeArea != nil {
                      addressString = addressString + place.subAdministrativeArea! + ", "
                  }
                  if place.country != nil {
                      addressString = addressString + place.country!
                  } 
      
                  return addressString
              }
              return ""
          }
      }
      

      然后执行:

          ReverseGeocoding.geocode(coordinate: coordinate, completion: { (placeMark, completeAddress, error) in
      
              if let placeMark = placeMark, let completeAddress = completeAddress {
                  print(placeMark.postalCode)
                  print(placeMark)
                  print(completeAddress)
              } else {
                  // do something with the error
              }
      

      终于打印出来了:

      15172
      Calle del Arenal, 4, Calle del Arenal, 4, 15172 Oleiros, A Coruña, España @ <+43.33190337,-8.37144380> +/- 100.00m, region CLCircularRegion (identifier:'<+43.33190337,-8.37144380> radius 70.84', center:<+43.33190337,-8.37144380>, radius:70.84m)
      Calle del Arenal, 4, Oleiros, 15172, A Coruña, España
      

      【讨论】:

        【解决方案10】:
        func convertLatLongToAddress(latitude:Double, longitude:Double) {
            let geoCoder = CLGeocoder()
            let location = CLLocation(latitude: latitude, longitude: longitude)
            var labelText = ""
            geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
        
                var placeMark: CLPlacemark!
                placeMark = placemarks?[0]
        
                if placeMark != nil {
                    if let name = placeMark.name {
                        labelText = name
                    }
                    if let subThoroughfare = placeMark.subThoroughfare {
                        if (subThoroughfare != placeMark.name) && (labelText != subThoroughfare) {
                            labelText = (labelText != "") ? labelText + "," + subThoroughfare : subThoroughfare
                        }
                    }
                    if let subLocality = placeMark.subLocality {
                        if (subLocality != placeMark.subThoroughfare) && (labelText != subLocality) {
                            labelText = (labelText != "") ? labelText + "," + subLocality : subLocality
                        }
                    }
                    if let street = placeMark.thoroughfare {
                        if (street != placeMark.subLocality) && (labelText != street) {
                            labelText = (labelText != "") ? labelText + "," + street : street
                        }
                    }
                    if let locality = placeMark.locality {
                        if (locality != placeMark.thoroughfare) && (labelText != locality) {
                            labelText = (labelText != "") ? labelText + "," + locality : locality
                        }
                    }
                    if let city = placeMark.subAdministrativeArea {
                        if (city != placeMark.locality) && (labelText != city) {
                            labelText = (labelText != "") ? labelText + "," + city : city
                        }
                    }
                    if let state = placeMark.postalAddress?.state {
                        if (state != placeMark.subAdministrativeArea) && (labelText != state) {
                            labelText = (labelText != "") ? labelText + "," + state : state
                        }
        
                    }
                    if let country = placeMark.country {
                        labelText = (labelText != "") ? labelText + "," + country : country
                    }
                    // labelText gives you the address of the place
                }
            })
        }
        

        作为改进,我还添加了地名。它使地址更有意义。

        【讨论】:

          【解决方案11】:
          func getAddress(from coordinate: CLLocationCoordinate2D, completion: @escaping (String) -> Void) {
                  let geoCoder = CLGeocoder()
                  let location = CLLocation.init(latitude: coordinate.latitude, longitude: coordinate.longitude)
                  
                  geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
                      
                      // check for errors
                      guard let placeMarkArr = placemarks else {
                          completion("")
                          debugPrint(error ?? "")
                          return
                      }
                      // check placemark data existence
                      
                      guard let placemark = placeMarkArr.first, !placeMarkArr.isEmpty else {
                          completion("")
                          return
                      }
                      // create address string
                      
                      let outputString = [placemark.locality,
                                          placemark.subLocality,
                                          placemark.thoroughfare,
                                          placemark.postalCode,
                                          placemark.subThoroughfare,
                                          placemark.country].compactMap { $0 }.joined(separator: ", ")
                      
                      completion(outputString)
                  })
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-12-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多