【问题标题】:CLPlacemark to string in iOS 9CLPlacemark 到 iOS 9 中的字符串
【发布时间】:2015-10-27 21:49:28
【问题描述】:

我想将CLPlacemark 格式化为字符串。

众所周知的方法是使用ABCreateStringWithAddressDictionary,但它在iOS 9中已被弃用。警告告诉我改用CNPostalAddressFormatter

但是,CNPostalAddressFormatter 只能格式化 CNPostalAddress。无法将CLPlacemark 正确转换为CNPostalAddressCLPlacemarkCNPostalAddress 仅共享这 3 个属性:countryISOcountryCodepostalCode

那么我现在应该如何将CLPlacemark 格式化为字符串呢?

【问题讨论】:

    标签: ios objective-c swift geocoding


    【解决方案1】:

    获取地标的addressDictionary 并使用其"FormattedAddressLines" 键提取地址字符串。请注意,这是字符串行的数组

    (不过,您说得对,负责转换为 Contacts 框架的 Apple 开发人员似乎完全忘记了 Address Book 和 CLPlacemark 之间的互换。这是 Contacts 框架中的一个严重错误 - 众多错误之一。)


    编辑 因为我最初发布了这个答案,Apple 修复了这个错误。 CLPlacemark 现在有一个postalAddress 属性,它是一个 CNPostalAddress,然后您可以使用 CNPostalAddressFormatter 来获得一个不错的多行地址字符串。一定要import Contacts

    【讨论】:

    • 读者注意:在给出这个正确答案后的 2 年多时间里,addressDictionary 已经贬值(在 iOS 11 中)。技术的巨无霸在...
    • ...和CLPlacemark 得到var postalAddress: CNPostalAddress? { get } 属性,锁住了整个线程!
    • @AmitaiB 是的,现在它纯粹是为了历史利益......!
    【解决方案2】:

    斯威夫特 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, United States”。
    【解决方案3】:

    Swift 4.1(和 3 & 4,保存 1 行)

    我读了这个问题来问“我该如何实现这个?”:

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

    两种方法

    我首先是移植 AddressDictionary 方法,其他发帖者也是如此。但这意味着失去CNPostalAddress 类和格式化程序的功能和灵活性。因此,方法2。

    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 中有效,除了用于删除 nil 值的 flatMap 在 Swift 4.1 中已弃用/重命名为 compactMap(文档 here,或参见 SE-187 了解基本原理)。

    【讨论】:

    • 您可以通过以下方式显着改进您的代码 1. 在示例代码中不使用其他库 2. 使用 flatMap + joined 3. null 合并,因为 CNPostalAddress 的所有成员都不为 null。 ps:那你就不用承诺免费的T恤了:P
    • @Andy 1] 谢谢,隐藏的扩展已删除。 2]flatMap + joined。唔。我会在 cmets 中添加一个替代方案,告诉我你是否更喜欢它。 3] 因为不能将它们分配给CLPlacemark 的String Optionals -- CLPlacemark 的属性需要合并,而不是CNPostalAddress 的。
    • 不管怎样,在 iOS 11 中 CLPlacemark 有一个 CNPostalAddress getter。
    【解决方案4】:

    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
          }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多