Table of Contents
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.
Enter
NSLocationWhenInUseUsageDescription
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.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.
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.
I hope that this tutorial explained the basics of GeoLocation and how we can utilize it in our App projects. Leave some love 🙂