iOS 9中将CLPlacemark转换为字符串

24
我想将CLPlacemark格式化为字符串。
众所周知的方法是使用ABCreateStringWithAddressDictionary,但在iOS 9中被弃用。警告告诉我要改用CNPostalAddressFormatter
然而,CNPostalAddressFormatter只能格式化CNPostalAddress。没有办法将CLPlacemark正确地转换为CNPostalAddress;只有这3个属性是CLPlacemarkCNPostalAddress共享的:countryISOcountryCodepostalCode
那么现在我应该如何将CLPlacemark格式化为字符串?
4个回答

28
使用 placemark 的 addressDictionary 属性,并使用其中的"FormattedAddressLines"键提取地址字符串。请注意,这是一个包含字符串行的数组。

(然而,你是正确的,苹果开发人员负责将 Address Book 转换为 Contacts 框架,似乎完全忘记了 Address Book 和 CLPlacemark 之间的互换。这是 Contacts 框架中的一个严重错误-很多错误之一。)


编辑 由于我最初发布该答案后,苹果已经修复了这个错误。现在,一个 CLPlacemark 具有一个postalAddress属性,该属性是 CNPostalAddress,您可以使用 CNPostalAddressFormatter 来获得漂亮的多行地址字符串。记得import Contacts


1
读者注意:自从这个正确答案被给出后已经过去了2年多的时间,addressDictionary在iOS 11中已经被废弃。技术巨头正在不断向前推进... - AmitaiB
...而CLPlacemark得到了var postalAddress: CNPostalAddress? { get }属性,整个线程都被Sherlock了! - AmitaiB
@AmitaiB 是的,现在它纯粹只是历史上的兴趣点了...! - matt

15

Swift 3.0

if let lines = myCLPlacemark.addressDictionary?["FormattedAddressLines"] as? [String] {
    let placeString = lines.joined(separator: ", ")
    // Do your thing
}

FormattedAddressLines 返回一个数组,其顺序为:名称、街道、城市、州、邮编、国家。如果您打印 placeString,您会看到类似于这样的内容:"Apple Inc.,1 Infinite Loop,Cupertino,CA 95014,美国"。 - Marlon Ruiz

8

Swift 4.1(还有3和4,只需保存1行)

我理解这个问题是在问“我该如何实现这个?”:

extension String {
    init?(placemark: CLPlacemark?) {
        // Yadda, yadda, yadda
    }
}

两种方法

我首先尝试了转移AddressDictionary方法,与其他发帖者一样。但这意味着失去了CNPostalAddress类和格式化器的强大灵活性。因此,采用第二种方法。

extension String {
    // original method (edited)
    init?(depreciated placemark1: CLPlacemark?) {
    // UPDATE: **addressDictionary depreciated in iOS 11**
        guard
            let myAddressDictionary = placemark1?.addressDictionary,
            let myAddressLines = myAddressDictionary["FormattedAddressLines"] as? [String]
    else { return nil }

        self.init(myAddressLines.joined(separator: " "))
}

    // my preferred method - let CNPostalAddressFormatter do the heavy lifting
    init?(betterMethod placemark2: CLPlacemark?) {
        // where the magic is:
        guard let postalAddress = CNMutablePostalAddress(placemark: placemark2) else { return nil }
        self.init(CNPostalAddressFormatter().string(from: postalAddress))
    }
}

等等,那个CLPlacemarkCNPostalAddress的初始化器是什么?

extension CNMutablePostalAddress {
    convenience init(placemark: CLPlacemark) {
        self.init()
        street = [placemark.subThoroughfare, placemark.thoroughfare]
            .compactMap { $0 }           // remove nils, so that...
            .joined(separator: " ")      // ...only if both != nil, add a space.
    /*
    // Equivalent street assignment, w/o flatMap + joined:
        if let subThoroughfare = placemark.subThoroughfare,
            let thoroughfare = placemark.thoroughfare {
            street = "\(subThoroughfare) \(thoroughfare)"
        } else {
            street = (placemark.subThoroughfare ?? "") + (placemark.thoroughfare ?? "")
        } 
    */
        city = placemark.locality ?? ""
        state = placemark.administrativeArea ?? ""
        postalCode = placemark.postalCode ?? ""
        country = placemark.country ?? ""
        isoCountryCode = placemark.isoCountryCode ?? ""
        if #available(iOS 10.3, *) {
            subLocality = placemark.subLocality ?? ""
            subAdministrativeArea = placemark.subAdministrativeArea ?? ""
        }
    }
}

使用方法

func quickAndDirtyDemo() {
    let location = CLLocation(latitude: 38.8977, longitude: -77.0365)

    CLGeocoder().reverseGeocodeLocation(location) { (placemarks, _) in
        if let address = String(depreciated: placemarks?.first) {
            print("\nAddress Dictionary method:\n\(address)") }

        if let address = String(betterMethod: placemarks?.first) {
            print("\nEnumerated init method:\n\(address)") }
    }
}

/* Output:
Address Dictionary method:
The White House 1600 Pennsylvania Ave NW Washington, DC  20500 United States

Enumerated init method:
1600 Pennsylvania Ave NW
Washington DC 20500
United States
*/

读到这里的人可以获得一件免费的T恤。(并不是真的)

*此代码可在Swift 3和4中使用,除了在Swift 4.1中已经弃用/重命名为compactMapflatMap用于删除空值(文档在这里,或者查看SE-187的原因)。


你可以通过以下三种方式显著改进你的代码:1. 在示例代码中不使用其他库;2. 使用 flatMap + joined;3. 使用 null 合并,因为 CNPostalAddress 的所有成员都是非空的。顺便说一句,这样你就不必承诺免费 T 恤了 :P - pronebird
1
@Andy 1] 谢谢,隐藏的扩展名已经移除。2] flatMap + joined. 嗯,我会在评论中添加一个替代方案,请告诉我您是否更喜欢它。3] 正是因为这个事实,他们不能被分配为 CLPlacemark 的字符串可选项 -- CLPlacemark 的属性需要合并,而不是 CNPostalAddress 的。 - AmitaiB
1
说句题外话,在iOS 11中,CLPlacemark有一个CNPostalAddress的getter。 - Mr Rogers

2

Swift 3.0 辅助方法

class func addressFromPlacemark(_ placemark:CLPlacemark)->String{
        var address = ""

        if let name = placemark.addressDictionary?["Name"] as? String {
            address = constructAddressString(address, newString: name)
        }

        if let city = placemark.addressDictionary?["City"] as? String {
            address = constructAddressString(address, newString: city)
        }

        if let state = placemark.addressDictionary?["State"] as? String {
            address = constructAddressString(address, newString: state)
        }

        if let country = placemark.country{
          address = constructAddressString(address, newString: country)
        }

        return address
      }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接