Как получить отформатированный адрес NSString из AddressDictionary?

Пытаюсь получить отформатированный адрес из AddressDictionary, который я получил от CLGeocoder. Использовал следующий код без результата:

subtitle = [NSString stringWithString:[[addressDict objectForKey:@"FormattedAddressLines"]objectAtIndex:0]];

Также пробовал:

subtitle = [[[ABAddressBook sharedAddressBook] formattedAddressFromDictionary:placemark.addressDictionary] string];

но этот код работает только на Mac OS X.

Компилятор спрашивает об ABAdressBook, но оба файла заголовков импортированы.

#import <AddressBook/ABAddressBook.h>
#import <AddressBook/AddressBook.h>

person Shmidt    schedule 21.10.2011    source источник


Ответы (10)


В документации для свойства addressDictionary говорится:

Вы можете отформатировать содержимое этого словаря, чтобы получить полную адресную строку, а не создавать адрес самостоятельно. Чтобы отформатировать словарь, используйте функцию ABCreateStringWithAddressDictionary, как описано в Справочнике по функциям пользовательского интерфейса адресной книги.

Поэтому добавьте и импортируйте фреймворк AddressBookUI и попробуйте:

subtitle = 
    ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO);
person Community    schedule 21.10.2011
comment
Это правильный метод... Однако он не всегда дает полный адрес. Например, поиск Уолл-стрит с помощью CLGeocoder вернет метку, а вызов ABCreateStringWithAddressDictionary в словаре меток вернет Нью-Йорк, Нью-Йорк, США... А Уолл-стрит нигде не найти. - person MiKL; 03.10.2013
comment
Этот метод также добавляет странные символы ascii, которые в последний раз проверялись в iOS8. - person jdog; 01.10.2014
comment
к сведению: ABCreateStringWithAddressDictionary устарел в iOS 9.0. - person Martyn Davis; 11.06.2015
comment
addressDictionary устарело в iOS 11. - person Supertecnoboff; 29.11.2017

Покопавшись под iOS 6.1, я обнаружил, что адресный словарь CLPlacemark содержит предварительно отформатированный адрес:

CLLocation *location = [[CLLocation alloc]initWithLatitude:37.3175 longitude:-122.041944];
[[[CLGeocoder alloc]init] reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
    CLPlacemark *placemark = placemarks[0];
    NSArray *lines = placemark.addressDictionary[ @"FormattedAddressLines"];
    NSString *addressString = [lines componentsJoinedByString:@"\n"];
    NSLog(@"Address: %@", addressString);
}];

Я пока не смог найти документацию об этом, но это работает для всех адресов, которые я тестировал.

person Klaas    schedule 11.08.2013
comment
Это то же самое, что и в вопросе. - person Shmidt; 12.08.2013
comment
Да, но вы сказали, что это НЕ работает. Мои тесты на всех моих устройствах и в симуляторе показывают, что это действительно работает. По крайней мере, с самыми последними версиями iOS. - person Klaas; 12.08.2013
comment
это хороший ответ, но для того, чтобы получить ПОЛНЫЙ адрес, мне пришлось использовать его вместо этого NSString *addressString = [lines componentsJoinedByString:@", "]; - person sudo; 13.03.2014
comment
@DigitalChild вы можете соединять строки любой строкой. Я предполагаю, что вы не видели полный адрес, потому что я использовал символ новой строки. - person Klaas; 13.03.2014
comment
Когда я ищу Apple в Пало-Альто, я получаю два результата Apple Store. Также нет ключа "FormattedAddressLines" в его addressDictionary. - person ma11hew28; 30.08.2014
comment
@MattDiPasquale, как искать Apple в Пало-Альто? Вы используете класс CLGeocoder? - person Klaas; 31.08.2014
comment
@Класс нет, я использую MKLocalSearch. - person ma11hew28; 31.08.2014
comment
@MattDiPasquale хорошо, может быть, "FormattedAddressLines" появляется только после вызова обратного геокодирования. - person Klaas; 01.09.2014

Как подчеркнул Мартин Дэвис, ABCreateStringWithAddressDictionary устарела в iOS 9.

Вы можете использовать приведенные ниже функции, чтобы преобразовать addressDictionary в более новую CNMutablePostalAddress, а затем использовать CNPostalAddressFormatter для создания локализованной строки, пока вы импортируете структуру Contacts.

Свифт 3.x

// Convert to the newer CNPostalAddress
func postalAddressFromAddressDictionary(_ addressdictionary: Dictionary<NSObject,AnyObject>) -> CNMutablePostalAddress {
   let address = CNMutablePostalAddress()

   address.street = addressdictionary["Street" as NSObject] as? String ?? ""
   address.state = addressdictionary["State" as NSObject] as? String ?? ""
   address.city = addressdictionary["City" as NSObject] as? String ?? ""
   address.country = addressdictionary["Country" as NSObject] as? String ?? ""
   address.postalCode = addressdictionary["ZIP" as NSObject] as? String ?? ""

   return address
}

// Create a localized address string from an Address Dictionary
func localizedStringForAddressDictionary(addressDictionary: Dictionary<NSObject,AnyObject>) -> String {
    return CNPostalAddressFormatter.string(from: postalAddressFromAddressDictionary(addressDictionary), style: .mailingAddress)
}

Свифт 2.х

import Contacts

// Convert to the newer CNPostalAddress
func postalAddressFromAddressDictionary(addressdictionary: Dictionary<NSObject,AnyObject>) -> CNMutablePostalAddress {

    let address = CNMutablePostalAddress()

    address.street = addressdictionary["Street"] as? String ?? ""
    address.state = addressdictionary["State"] as? String ?? ""
    address.city = addressdictionary["City"] as? String ?? ""
    address.country = addressdictionary["Country"] as? String ?? ""
    address.postalCode = addressdictionary["ZIP"] as? String ?? ""

    return address
}

// Create a localized address string from an Address Dictionary
func localizedStringForAddressDictionary(addressDictionary: Dictionary<NSObject,AnyObject>) -> String {

    return CNPostalAddressFormatter.stringFromPostalAddress(postalAddressFromAddressDictionary(addressDictionary), style: .MailingAddress)
}
person tebs1200    schedule 18.12.2015
comment
NB: этот API доступен начиная с iOS9. - person Matthieu Riegler; 06.01.2016
comment
Спасибо за это. К вашему сведению, в документах для CNPostalAddressFormatter.stringFromPostalAddress не упоминается второй параметр style, но сборка завершается сбоем, если он у вас есть. Взгляните на документы: developer.apple.com/library/ios/documentation/Contacts/: - person Joshua Pinter; 11.05.2016
comment
Разве использование ZIP не делает это специфичным только для американских адресов? - person shim; 27.11.2016
comment
addressDictionary, включая поле ZIP, — это просто формат, в котором геокодер возвращает результат. ZIP — это имя поля, но геокодер помещает эквивалентное значение (например, почтовый индекс) в это поле, где это применимо, насколько мне известно. . Я не живу в Америке, и это работает для меня. - person tebs1200; 02.12.2016
comment
CNPostalAddressFormatter, похоже, не включает разделители в строку. Например, в американском адресе нет запятой между городом и штатом. Есть ли способ включить это? - person Crashalot; 09.12.2018

func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    // get the address
    if let location = locations.last {
        CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (result: [CLPlacemark]?, err: NSError?) -> Void in
            if let placemark = result?.last
                , addrList = placemark.addressDictionary?["FormattedAddressLines"] as? [String]
            {
                let address =  addrList.joinWithSeparator(", ")
                print(address)
            }
        })
    }
}

Выше приведена быстрая версия.

person ZYiOS    schedule 29.03.2015
comment
По-видимому, подписка addressDictionary больше не работает в iOS 9. - person Frédéric Adda; 25.09.2015
comment
@ФредА. Я обновил код до последнего быстрого синтаксиса. - person ZYiOS; 15.12.2015
comment
По состоянию на 3 февраля 2017 г. Swift требует дополнительного разрешения перед addrList, а joinWithSeparator теперь только что присоединился. - person Tad Donaghe; 04.02.2017
comment
почему для получения метки требуется две-три секунды - person Jaydeep Vyas; 08.07.2017

Я использую Swift 3/XCode 8

Ответ ZYiOS был красивым и коротким, но не скомпилировался для меня.

Вопрос спрашивает, как перейти от существующего словаря адресов к строковому адресу. Вот что я сделал:

import CoreLocation

func getAddressString(placemark: CLPlacemark) -> String? {
    var originAddress : String?

    if let addrList = placemark.addressDictionary?["FormattedAddressLines"] as? [String]
    {
        originAddress =  addrList.joined(separator: ", ")
    }

    return originAddress
}
person Nick Graham    schedule 04.10.2016

Swift 3 / Xcode 8 Helper Mehtod для получения адреса из CLPlaceMark

class func formattedAddress(fromPlacemark 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
  }
person Sourabh Sharma    schedule 28.10.2016

Теперь это так же просто, как

func updateUserAddress(coordinates: CLLocationCoordinate2D) {
    let geoCoder = CLGeocoder()
    let location = CLLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
    geoCoder.reverseGeocodeLocation(location) {[weak self] (placemarks, error) in
        if error == nil, let placemark = placemarks?.first, let address = placemark.postalAddress {
            self?.userLocationLabel.text = CNPostalAddressFormatter.string(from: address, style: .mailingAddress)
        }
    }
}
person Yuri Grigoriev    schedule 17.07.2020

Просто создайте расширение для CLLocation:

typealias AddressDictionaryHandler = ([String: Any]?) -> Void

extension CLLocation {

    func addressDictionary(completion: @escaping AddressDictionaryHandler) {

        CLGeocoder().reverseGeocodeLocation(self) { placemarks, _ in
            completion(placemarks?.first?.addressDictionary as? [String: AnyObject])
        }
    }
}

Пример:

let location = CLLocation()

location.addressDictionary { dictionary in

    let city = dictionary?["City"] as? String
    let street = dictionary?["Street"] as? String
}
person Bartłomiej Semańczyk    schedule 10.05.2017

Версия Swift 5

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

            if let addrList = place.addressDictionary?["FormattedAddressLines"] as? [String] {
                let addressString = addrList.joined(separator: ", ")
                print(addressString)

            }
        }
person mini coder    schedule 19.12.2019

iOS 11+

import CoreLocation
import Contacts

public extension CLPlacemark {
    func formattedAddress() -> String? {
        guard let postalAddress = postalAddress else { return nil }
        let formatter = CNPostalAddressFormatter()
        formatter.style = .mailingAddress
        let formatterString = formatter.string(from: postalAddress)
        return formatterString.replacingOccurrences(of: "\n", with: " ")
    }
}
person hstdt    schedule 08.05.2021