Swift - Generate an Address Format from Reverse Geocoding
Asked Answered
J

12

38

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;
    } 
Jaella answered 28/12, 2016 at 8:49 Comment(2)
See my answer in swift 4.1 Xcode 9.4.1. You can get even village name details also. #16648496Hower
addressDictionary is deprecated in iOS 11 and onwardsVexatious
P
91
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)
              }
        })

    }
Pernickety answered 28/12, 2016 at 8:57 Comment(10)
and last print addressstring . you got your whole addressPernickety
There's an issue with this. The func goes through this process before waiting for the address to be processed from the LatLng. So therefore I end up with an empty stringJaella
@RickGrimesLikesWalkerSoup check my whole method that i use in my project . you can also use itPernickety
You are a beautiful human being. This works like a charm. Thanks dude!Jaella
@RickGrimesLikesWalkerSoup :)Pernickety
@HimanshuMoradiya: Works like a charm, Thanks a lot, but one thing to mention, you should not force unwrap placemark, cause it may be nil sometimes which happened with me, use a if let to get rid of unwanted crashes. :P Cheers. Happy Coding.Multiracial
Add each address strings to an array and then use let addressString = addArray.joined(separator: ",") to convert into StringHaygood
let pm = placemarks! as [CLPlacemark] gives me an error: "warning: could not execute support code to read Objective-C class data in the process"Fichu
let placemark = placemarks! as [CLPlacemark] this line error showing you right ? @grantPernickety
How to return address?? I am not able to return. Pls helpDavinadavine
M
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.

Martinsen answered 12/8, 2018 at 12:30 Comment(8)
I like this approach but when I add your extension I'm seeing 'postalAddress' used within it's own type?Proudfoot
Do not add the extension. I'm showing the Extension that Apple added. Just remove it and everything should be fine.Martinsen
oh haha, my bad.Proudfoot
Life saver. Thanks for the help.Jenellejenesia
@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....Boot
@Boot the only usable information in ˚MKPointAnnotation˚ is the coordinate. So you create a CLLocation from the coordinates, then a CLPLacemark from your CLLocation.Martinsen
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-PlacemarksBoot
@Boot name and address dictionary are optional in the constructor. You can set them to nil.Martinsen
W
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)
Wolffish answered 11/8, 2017 at 20:17 Comment(1)
let place = placemark! as [CLPlacemark] do not force unwrap it will crash whole app on no networkPhenix
B
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)
}
Banded answered 28/12, 2016 at 9:18 Comment(2)
Gives error on this line. geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in "Escaping closure captures non-escaping parameter 'handler'"Heldentenor
@Heldentenor In later swift versions you have to write @escaping for closures, updated the answer. Please have a look.Banded
K
3

To concatenate you can simply replace return address by this :

return "\(locationName), \(street), \(city), \(zip), \(country)"
Ketubim answered 28/12, 2016 at 9:2 Comment(3)
This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. - From ReviewLunatic
@Mack This answer to the "how to concatenate..."Ketubim
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.Lunatic
D
3
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)
        })
    }
Delmydeloach answered 21/9, 2021 at 12:26 Comment(0)
T
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.

Tarrant answered 9/11, 2017 at 2:44 Comment(2)
what about the street address?Boot
@Boot 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 developer.apple.com/documentation/corelocation/clplacemark/…Tarrant
A
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
    }
Aroid answered 18/2, 2020 at 6:34 Comment(0)
H
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)}
})
Haygood answered 31/8, 2018 at 12:31 Comment(0)
B
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
Braswell answered 15/2, 2019 at 13:20 Comment(0)
O
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.

Oestrone answered 15/5, 2020 at 8:17 Comment(0)
S
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)
      }
    }
  }
Sarcophagus answered 28/3, 2022 at 10:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.