Swift 101. Convert coordinates to city names and back

Swift 101: Converting Coordinates to City Names & Vice Versa - Unlock the power of geolocation in your Swift applications with this practical guide. Learn how to effortlessly translate coordinates into city names and reverse the process, enhancing your app's location-based features. A must-read for developers keen on integrating advanced geocoding capabilities into their iOS projects.

Swift 101. Convert coordinates to city names and back
Do not index
Do not index
In this article I will explain how to get the names of the city, country, zip code, etc from a geo location object and the other way around.
The first thing we need to do is to set the needed permissions into the Info.plist file to be able to ask the User to access the GPS module of this phone. Open the Info.plist and set the following.
notion image
EnterNSLocationWhenInUseUsageDescription into the key field and press return. It will automatically turn into “Privacy — Location When…”. Same for NSLocationAlwaysUsageDescription .
Make sure to add a valid message, describing why the App needs the location permission, into the value field. A good text is important as Apple will reject the App when it just says “lorem impsum”. Been there. Literally done that 😁
You can chose whether you want to use “Location When In Use” or “Always”. Logically the first uses the GPS module only when the App is in opened. The latter uses the location even if the App is closed. Don’t use the second version unless you have a very good reason. Recently there has been US wide rioting in the streets when Uber changed the settings to “Always” for no obvious reason but tracking Users. Maybe not so literally 🤪

Geo location to city name

First we will create a helper class that contains our logic to generate city names from locations and back as well as get the devices location.
We will call it LocationManager.swift and most importantly we will need to import the CoreLocation framework.
In our class we’re creating an instance object of the core location manager and we’re setting the delegate, setting the desired accuracy to best and we’re asking for permission to use the iPhones location service.
Important: In our example we’re expecting the User (you) to allow the permissions. We’re not going to handle denying cases as it would exceed the length of this article. In your App you should definitely handle those cases.
import Foundation
import CoreLocation


class LocationManager: NSObject {
    
    
    private let locationManager = CLLocationManager()
    
    override init() {
        super.init()
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.requestWhenInUseAuthorization()
    }
}


// MARK: - Core Location Delegate
extension LocationManager: CLLocationManagerDelegate {
    
    
    func locationManager(_ manager: CLLocationManager,
                         didChangeAuthorization status: CLAuthorizationStatus) {

        switch status {
    
        case .notDetermined         : print("notDetermined")        // location permission not asked for yet
        case .authorizedWhenInUse   : print("authorizedWhenInUse")  // location authorized
        case .authorizedAlways      : print("authorizedAlways")     // location authorized
        case .restricted            : print("restricted")           // TODO: handle
        case .denied                : print("denied")               // TODO: handle
        }
    }
}
Now when we create an instance of our LocationManager in our example ViewController, we will see the following once we run the App.
notion image
Our next goal is to display our current location’s address values into a label on our example ViewController. We will add a function that takes our current iPhone’s location and uses Apple’s GLGeocoder class and its reverseGeocodeLocation function.
We’re first checking if the error is nil, if not then we stop here (using return) and printing the Error. Same to check if the placemark object exists. This object might be nil. So handle this case in your App by presenting an error. It’s important to give the User a good experience.
Important: Apple wants us to create a new GLGeocoder object for every process call. For example whenever you want to call a new placemark object for a location, use a new GLGeocoder object. That’s why the object is part of the function, not part of the class, as we would usually handle repeatedly called objects.
If everything works as expected we’re returning the placemark object in our completion handler (async).
// MARK: - Get Placemark
extension LocationManager {
    
    
    func getPlace(for location: CLLocation,
                  completion: @escaping (CLPlacemark?) -> Void) {
        
        let geocoder = CLGeocoder()
        geocoder.reverseGeocodeLocation(location) { placemarks, error in
            
            guard error == nil else {
                print("*** Error in \(#function): \(error!.localizedDescription)")
                completion(nil)
                return
            }
            
            guard let placemark = placemarks?[0] else {
                print("*** Error in \(#function): placemark is nil")
                completion(nil)
                return
            }
            
            completion(placemark)
        }
    }
}
In our calling side, we’re using the LocationManager to get the current location and call the getPlace function to get the placemark object. Using Apple’s documentation we can see what values are available for us. We will use this knowledge to populate our label like following.
It’s not a pretty way, but it’s good enough for this example and to keep it simple/short. A better way is to create a helper function in our LocationManager.
First we will add the exposedLocation object to our LocationManager class.
class LocationManager: NSObject {
    
    
    // - Private
    private let locationManager = CLLocationManager()
    
    
    // - API
    public var exposedLocation: CLLocation? {
        return self.locationManager.location
    }
    
    override init() {
        super.init()
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.requestWhenInUseAuthorization()
    }
}
Next we will call our getPlace function and use the result to create text for our label that contains our current location, state and town. We will safely unwrap each optional value and use\n to create a line break.
class ViewController: UIViewController {

    
    // - Outlets
    @IBOutlet weak var locationLabel: UILabel!
    
    
    // - Constants
    private let locationManager = LocationManager()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let exposedLocation = self.locationManager.exposedLocation else {
            print("*** Error in \(#function): exposedLocation is nil")
            return
        }
        
        self.locationManager.getPlace(for: exposedLocation) { placemark in
            guard let placemark = placemark else { return }
            
            var output = "Our location is:"
            if let country = placemark.country {
                output = output + "\n\(country)"
            }
            if let state = placemark.administrativeArea {
                output = output + "\n\(state)"
            }
            if let town = placemark.locality {
                output = output + "\n\(town)"
            }
            self.locationLabel.text = output
        }
    }
}
The result in the simulator will be San Francisco. As this is the default simulator location. In your physical device you’ll see your location.
notion image
Simulator label text

Get the geo location from a location text

Lastly we want to take a text location and return a geo location. For our example we will use the location of the Eiffel Tower in Paris, France and display the location on a map view. According to Google the address is “Champ de Mars, 5 Avenue Anatole France, 75007 Paris, France”.
Now we’ll create afunction that uses again the GLGeocoder , but this time the function geoAddressString , to get again a placemark object. This time using the location object of the placemark.
// MARK: - Get Location
extension LocationManager {
    
    
    func getLocation(forPlaceCalled name: String,
                     completion: @escaping(CLLocation?) -> Void) {
        
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(name) { placemarks, error in
            
            guard error == nil else {
                print("*** Error in \(#function): \(error!.localizedDescription)")
                completion(nil)
                return
            }
            
            guard let placemark = placemarks?[0] else {
                print("*** Error in \(#function): placemark is nil")
                completion(nil)
                return
            }
            
            guard let location = placemark.location else {
                print("*** Error in \(#function): placemark is nil")
                completion(nil)
                return
            }

            completion(location)
        }
    }
}
In this example again we’ll just return and stop on any possibly upcoming issue. We’ll check for the error, then placemark and lastly the location object of the placemark. If everything is as expected, we will return the location in our completion handler (async).
Calling the new function as following, adding a MapView to our example ViewController, importing MapKit and cleaning up the code a bit we end up with the following call side implementation:
import UIKit
import MapKit


class ViewController: UIViewController {

    
    // - Outlets
    @IBOutlet weak var locationLabel: UILabel!
    @IBOutlet weak var mapView: MKMapView!
    
    
    // - Constants
    private let locationManager = LocationManager()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setCurrentLocation()
        self.setParisMapLocation()
    }
    
    private func setCurrentLocation() {
        
        guard let exposedLocation = self.locationManager.exposedLocation else {
            print("*** Error in \(#function): exposedLocation is nil")
            return
        }
        
        self.locationManager.getPlace(for: exposedLocation) { placemark in
            guard let placemark = placemark else { return }
            
            var output = "Our location is:"
            if let country = placemark.country {
                output = output + "\n\(country)"
            }
            if let state = placemark.administrativeArea {
                output = output + "\n\(state)"
            }
            if let town = placemark.locality {
                output = output + "\n\(town)"
            }
            self.locationLabel.text = output
        }
    }
    
    private func setParisMapLocation() {
        
        let eiffelTower = "Champ de Mars, 5 Avenue Anatole France, 75007 Paris, France"
        
        self.locationManager.getLocation(forPlaceCalled: eiffelTower) { location in
            guard let location = location else { return }
            
            let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
            let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
            self.mapView.setRegion(region, animated: true)
        }
    }
}
The result presents our simulator’s location on the top label and the centered location of the Eiffel Tower in Paris in the map view.
notion image
I hope that this tutorial explained the basics of GeoLocation and how we can utilize it in our App projects. Leave some love 🙂

Ready to take the next big step for your business?

Join other 3200+ marketers now!

Subscribe