ios Swift - 从反向地理编码生成地址格式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41358423/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 10:59:40  来源:igfitidea点击:

Swift - Generate an Address Format from Reverse Geocoding

iosswiftxcodegoogle-mapsreverse-geocoding

提问by Dinuka Jay

I am trying to generate a Formatted Full address using CLGeocoder in Swift 3. I referred to this SOthread to get the code given below.

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

However, sometimes the app crashes with a 'nil' error at the line:

但是,有时应用程序会崩溃并显示“nil”错误:

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

Questions:

问题:

  1. How can I concatenate these values retrieved from the GeoCoder to form a full address? (Street + City + etc)
  2. How do I handle the nil error I get when the func is unable to find an address?
  1. 如何连接从 GeoCoder 检索到的这些值以形成完整地址?(街道+城市+等)
  2. 当 func 无法找到地址时,我如何处理我得到的 nil 错误?

Full code:

完整代码:

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

回答by Himanshu Moradiya

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

    }

回答by Gerd Castan

Formatting addresses is hard because each country has its own format.

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

With a few lines of code, you can get the correct address format for each country and let Apple handle the differences.

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

Since iOS 11, you can get a Contacts framework address:

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

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

This extension is part of the Contactsframework. This means, this feature is invisible to you in the XCodecode completion until you do

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

import Contacts

With this additional import, you can do something like

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

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

and get the address formatted in the format for the country in the address.

并获取以地址china家/地区格式格式化的地址。

The formatter even supports formatting as an attributedString.

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

Prior to iOS 11, you can convert CLPlacemarkto CNPostalAddressyourself and still can use the country specific formatting of CNPostalAddressFormatter.

在 iOS 11 之前,您可以转换CLPlacemarkCNPostalAddress自己,并且仍然可以使用CNPostalAddressFormatter.

回答by oscar castellon

This is my code for swift 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
                }
            }
        }
  }

You can esaily call above funcation like:

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

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

回答by Makaille

To concatenate you can simply replace return addressby this :

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

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

回答by Midhun MP

  1. For fixing the empty address issue, either you can use a class property to hold the appended value or you can use a closure to return the value back to the calling function
  2. For fixing the crash you need to avoid the force unwrapping of optionals
  1. 为了解决空地址问题,您可以使用类属性来保存附加值,也可以使用闭包将值返回给调用函数
  2. 为了修复崩溃,您需要避免强制展开选项

Using a closure you can do it like:

使用闭包你可以这样做:

// Using closure
func getAddress(handler: (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)
    })
}

You can call the method like:

您可以调用该方法,如:

getAddress { (address) in
    print(address)
}

回答by elarctheitroadis

Keeping it simple - A full Swift 3 & 4 compatible View Controller example for obtaining a formatted address string from user's location (add in the other keys available in CLPlacemarkif you want more information in your string):

保持简单 - 一个完整的 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
}

}

Calling print(userLocationString()) in this example will print: suburb, state, country

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

Don't forget to add Privacy - Location When In Use Usage Descriptionto your Info.plist file beforehand, to allow the user to grant permissions to your app to utilise location services.

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

回答by Prashant Bhayani

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

回答by Javier Amor Penas

I create my own static class for Geocoding and get attributes of CLPlacemark and obtain a complete address, like "usually" returns Google:

我为地理编码创建了自己的静态类并获取 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 ""
    }
}

Then the implementation:

然后实现:

    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
        }

Finaly the print:

最后打印:

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

回答by Saud Waqar

Here's a 2-3 line version of the answers here:

这是这里答案的 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{
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
        }
    })
}
}.joined(separator: ", ") print(outputString) return outputString }```

回答by Wimukthi Rajapaksha

##代码##

Here as an improvement I added place name as well. It makes address more meaningful.

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