37

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

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

//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?

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;
    } 
Gerd Castan
  • 6,275
  • 3
  • 44
  • 89
Jay
  • 4,873
  • 7
  • 72
  • 137
  • See my answer in swift 4.1 Xcode 9.4.1. You can get even village name details also. https://stackoverflow.com/questions/16647996/get-location-name-from-latitude-longitude-in-ios/51797299#51797299 – Naresh Aug 11 '18 at 07:01
  • addressDictionary is deprecated in iOS 11 and onwards – Ashutosh Shukla Sep 18 '20 at 11:13

12 Answers12

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

    }
Himanshu Moradiya
  • 4,769
  • 4
  • 25
  • 49
26

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.

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

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

This extension is part of the Contacts framework. This means, this feature is invisible to you in the XCode code completion until you do

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.

The formatter even supports formatting as an attributedString.

Prior to iOS 11, you can convert CLPlacemark to CNPostalAddress yourself and still can use the country specific formatting of CNPostalAddressFormatter.

Gerd Castan
  • 6,275
  • 3
  • 44
  • 89
  • I like this approach but when I add your extension I'm seeing 'postalAddress' used within it's own type? – GarySabo Sep 06 '18 at 15:29
  • Do not add the extension. I'm showing the Extension that Apple added. Just remove it and everything should be fine. – Gerd Castan Sep 06 '18 at 15:45
  • oh haha, my bad. – GarySabo Sep 06 '18 at 15:51
  • 2
    Life saver. Thanks for the help. – Jake Aug 11 '19 at 04:25
  • @GerdCastan Is there a similar built-in function to help convert an `MKPointAnnotation` to a `CLPlacemark`? So far, all I can see is to create the addressDictionary myself.... – Zonker.in.Geneva Aug 26 '19 at 07:52
  • @Zonker.in.Geneva the only usable information in ˚MKPointAnnotation˚ is the coordinate. So you create a CLLocation from the coordinates, then a CLPLacemark from your CLLocation. – Gerd Castan Aug 26 '19 at 11:14
  • Right, but creating a Placemark requires an addressDictionary. And I can’t figure out how to set the name of a Placemark. And I can’t decide if I should is MK- or CL-Placemarks – Zonker.in.Geneva Aug 26 '19 at 11:17
  • @Zonker.in.Geneva name and address dictionary are optional in the constructor. You can set them to nil. – Gerd Castan Aug 26 '19 at 11:24
8

This is my code for 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)
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
oscar castellon
  • 3,048
  • 30
  • 19
6
  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

Using a closure you can do it like:

// Using closure
func getAddress(handler: @escaping (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)
}
Midhun MP
  • 103,496
  • 31
  • 153
  • 200
  • Gives error on this line. geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in "Escaping closure captures non-escaping parameter 'handler'" – Ashu Aug 14 '20 at 12:50
  • @Ashu In later swift versions you have to write `@escaping` for closures, updated the answer. Please have a look. – Midhun MP Aug 17 '20 at 04:56
3

To concatenate you can simply replace return address by this :

return "\(locationName), \(street), \(city), \(zip), \(country)"
Makaille
  • 1,636
  • 2
  • 24
  • 41
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. - [From Review](/review/low-quality-posts/14721054) – Mayur Prajapati Dec 28 '16 at 10:38
  • @Mack This answer to the "how to concatenate..." – Makaille Dec 28 '16 at 10:43
  • Probably you should add some more explanation about what to do, how to use, where to use this line & some thing. Now just simply placing 1 line doesn't always get proper solution. – Mayur Prajapati Dec 28 '16 at 10:45
2

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 CLPlacemark if you want more information in your string):

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

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

elarcoiris
  • 1,914
  • 4
  • 28
  • 30
  • what about the street address? – Zonker.in.Geneva Aug 26 '19 at 07:53
  • @Zonker.in.Geneva Check out the link in the explanation, more specifically 'thoroughfare'. In this example you would use placemark.subThoroughfare, placemark.thoroughfare, perhaps placemark.subLocality and placemark.postalCode https://developer.apple.com/documentation/corelocation/clplacemark/1423814-thoroughfare – elarcoiris Aug 26 '19 at 13:35
2

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

    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{$0}.joined(separator: ", ")
        print(outputString)
        return outputString
    }
Saud Waqar
  • 31
  • 2
2
func getAddress(from coordinate: CLLocationCoordinate2D, completion: @escaping (String) -> Void) {
        let geoCoder = CLGeocoder()
        let location = CLLocation.init(latitude: coordinate.latitude, longitude: coordinate.longitude)
        
        geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
            
            // check for errors
            guard let placeMarkArr = placemarks else {
                completion("")
                debugPrint(error ?? "")
                return
            }
            // check placemark data existence
            
            guard let placemark = placeMarkArr.first, !placeMarkArr.isEmpty else {
                completion("")
                return
            }
            // create address string
            
            let outputString = [placemark.locality,
                                placemark.subLocality,
                                placemark.thoroughfare,
                                placemark.postalCode,
                                placemark.subThoroughfare,
                                placemark.country].compactMap { $0 }.joined(separator: ", ")
            
            completion(outputString)
        })
    }
Pathak Ayush
  • 726
  • 12
  • 24
1
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)}
})
Prashant Bhayani
  • 692
  • 5
  • 18
1

I create my own static class for Geocoding and get attributes of CLPlacemark and obtain a complete address, like "usually" returns 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
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Javi AP
  • 392
  • 3
  • 8
1
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
        }
    })
}

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

Wimukthi Rajapaksha
  • 961
  • 1
  • 11
  • 23
0
 func getAddressFromlatLong(lat: Double, long: Double, completion: @escaping (_ address: String) -> Void){
    let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
    let geocoder = GMSGeocoder()
    var add = ""
    geocoder.reverseGeocodeCoordinate(coordinate) { (response, error) in
      if let address = response?.firstResult() {
        
        guard let arrAddress = address.lines else {return}
        if arrAddress.count > 1 {
            add =  /(arrAddress[0]) + ", " + /(arrAddress[1])
    
        }else if arrAddress.count == 1 {
            add =  /(arrAddress[0])
        }
        completion(add)
      }
    }
  }
Davender Verma
  • 503
  • 2
  • 12