How to get formatted address NSString from AddressDictionary?
Asked Answered
V

10

16

Trying to get formatted address from AddressDictionary, that I got from CLGeocoder. Used following code with no result:

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

Also tried:

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

but this code seems working on Mac OS X only.

Compiler asks about ABAdressBook, but I have both header files imported.

#import <AddressBook/ABAddressBook.h>
#import <AddressBook/AddressBook.h>
Vermin answered 21/10, 2011 at 10:42 Comment(0)
T
40

The documentation for the addressDictionary property says:

You can format the contents of this dictionary to get a full address string as opposed to building the address yourself. To format the dictionary, use the ABCreateStringWithAddressDictionary function as described in Address Book UI Functions Reference.

So add and import the AddressBookUI framework and try:

subtitle = 
    ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO);
Tribe answered 21/10, 2011 at 12:16 Comment(4)
This is the right method to use... However it does not always provide a complete address. For instance looking up "wall street" with CLGeocoder will return a placemark and calling ABCreateStringWithAddressDictionary on the placemark dictionary will return "New York, NY, United Stated"... And "Wall Street" is nowhere to be found.Creepie
This method also adds weird ascii characters, last checked in iOS8Hittel
fyi - ABCreateStringWithAddressDictionary has been deprecated in iOS 9.0Hadsall
addressDictionary is deprecated in iOS 11.Tallbott
A
24

After doing some digging under iOS 6.1 I found out that the CLPlacemark address dictionary contains a pre-formatted address:

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

I couldn't yet find documentation about this, but it works for all the addresses that I tested.

Algar answered 11/8, 2013 at 21:12 Comment(8)
It's the same as in question.Vermin
Yes, but you said it was NOT working. My tests on all my devices and in the simulator show that it is actually working. At least with the most recent iOS versions.Algar
this is a good answer but in order for me to get the FULL address I had to use this instead NSString *addressString = [lines componentsJoinedByString:@", "];Dronski
@DigitalChild you can join the lines by any string. I guess that you didn't see the full address because I used the new line character.Algar
When I search for "Apple" in Palo Alto, I get back two Apple Store results. Neither have the "FormattedAddressLines" key in its addressDictionary.Eloiseelon
@MattDiPasquale how to you search for "Apple" in Palo Alto? Are you using the CLGeocoder class?Algar
@Klass no, I'm using MKLocalSearch.Eloiseelon
@MattDiPasquale ok, might be that "FormattedAddressLines" is only there after issuing a reverse geocoding call.Algar
T
20

As highlighted by Martyn Davis, ABCreateStringWithAddressDictionary is deprecated in iOS 9.

You can use the functions below to convert the addressDictionary to the newer CNMutablePostalAddress, then use the CNPostalAddressFormatter to generate a localised string as long as you import the Contacts framework.

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

Swift 2.x

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)
}
Thenna answered 18/12, 2015 at 13:22 Comment(5)
NB: This API is available from iOS9 upwards.Complainant
Thanks for this. FYI, the docs for CNPostalAddressFormatter.stringFromPostalAddress make no mention of the second style parameter but the build fails unless you have it. Take a look at the docs: developer.apple.com/library/ios/documentation/Contacts/…:Garland
Doesn't the use of "ZIP" make this specific to American addresses only?Futilitarian
The addressDictionary including the ZIP field is simply the format that the geocoder returns the result in. ZIP is the name of the field, but the geocoder puts the equivalent value (like postal code) into this field where applicable as far as I'm aware. I don't live in America and it works for me.Thenna
CNPostalAddressFormatter doesn't seem to include delimiters in the string. For instance, there is no comma between city and state for an American address. Is there a way to enable this?Empanel
D
13
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)
            }
        })
    }
}

Above is the swift version.

Dawdle answered 29/3, 2015 at 10:21 Comment(4)
Apparently the subscripting of the addressDictionary does not work anymore in iOS 9Dari
@FredA. I have updated the code to latest swift syntax.Dawdle
As of Feb 3, 2017, swift is demanding an extra let in front of addrList and joinWithSeparator is now just joined.Bedtime
why this takes two to three seconds for getting placemarkGait
O
4

I am using Swift 3 / XCode 8

ZYiOS's answer was nice and short but did not compile for me.

The question asks how to get from an existing Address Dictionary to a string address. This is what I did:

import CoreLocation

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

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

    return originAddress
}
Obelia answered 4/10, 2016 at 21:12 Comment(0)
A
2

Swift 3 / Xcode 8 Helper Mehtod to get address from 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
  }
Appleby answered 28/10, 2016 at 11:52 Comment(0)
G
1

Now this is as simple as

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)
        }
    }
}
Goatee answered 17/7, 2020 at 8:13 Comment(0)
P
1

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: " ")
    }
}
Pushball answered 8/5, 2021 at 3:22 Comment(0)
M
0

Simply create extension for 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])
        }
    }
}

Example:

let location = CLLocation()

location.addressDictionary { dictionary in

    let city = dictionary?["City"] as? String
    let street = dictionary?["Street"] as? String
}
Magaretmagas answered 10/5, 2017 at 9:51 Comment(0)
B
0

Swift 5 version

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)

            }
        }
Bye answered 19/12, 2019 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.