Deep Dive into Core Location in iOS: Requesting and Utilizing User Location

Step-by-step guide to set up and use Core Location framework in your iOS app for obtaining user device location

Dwi Randy Herdinanto
8 min readApr 19, 2023

Outline

  • Overview
  • Setup The Project
  • Request Location Permission
  • Request One Time Location Information
  • Continuous Location Updates
  • Core Location Error Handling
  • Debugging in Core Location Simulator

Overview

Have you wondered how to get current location from user devices? or perhaps do you want to create feature based on user location? if so, gladly apple already provide powerful framework for obtaining a device’s location, altitude and orientation.

Core Location collects data using all available components on the device such as Wi-FI, GPS, Bluetooth, magnetometer, barometer and cellular hardware.

One of the most intersesting features of Core Location is geofencing and region monitoring, that allows us to defince regions and receive notification when the user enters or exists thos regions. All of this very cool feature can be utilized in many ways to solve user problem.

Setup The Project

Before we are able to use CoreLocation we need to fulfill some requirement in our xcode project. First of all we just need create a new iOS project from xcode. In this case i use a simple project that will use UIKit and Swift.

Setup Info.plist for Location Privacy

To get information about user location, we need to add some string in the info.plist file. This string message will be shown when application ask for permission

<dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app need your location to provide best feature based on location</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app need your location to provide best feature based on location</string>
</dict>
Authorization Dialog for requesting When In Use Usage Description
Authorization Dialog for Requesting Always and When In Use Usage Description

Use NSLocationAlwaysAndWhenInUseUsageDescription key if app accesses location information while running in the background. If your app only needs location information when in the foreground, use NSLocationWhenInUseUsageDescription instead.

You can adjust as you need but the message should fulfills the following requirements:

You can adjust as you need but the message should fulfills the following requirements:

  • Motivate users to grant you access to their information.
  • Clearly communicate the specific purpose and method of data collection.
  • Ensure that the statement is entirely accurate.

Project Capabilities

In case you want to retrieve location information in the background state, you need to ensure that the Location updates option is selected in the Background Modes tab.

Request Location Permission

We have setup our project, in the next step we want to explore how to request location permision, since location data is sensitive information, and the use of location data has privacy implications for the people who use your app.

Request Location When In Use Permission

When in Use authorization makes location updates available only when someone uses your app. This authorization is the preferred choice, because it has better privacy and battery life implications.

To do that we can use this code in our ViewController

import UIKit
// 1
import CoreLocation

class ViewController: UIViewController {

var locationManager: CLLocationManager?

override func viewDidLoad() {
super.viewDidLoad()

// 2
locationManager = CLLocationManager()
locationManager?.delegate = self


// 3
locationManager?.requestWhenInUseAuthorization()
}

}

extension ViewController: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
print("When user did not yet determined")
case .restricted:
print("Restricted by parental control")
case .denied:
print("When user select option Dont't Allow")
case .authorizedWhenInUse:
print("When user select option Allow While Using App or Allow Once")
default:
print("default")
}
}
}

Code explanation

  1. The first line of the code imports the Core Location framework, which provides the classes for working with location data.
  2. In the viewDidLoad() method, the code creates a CLLocationManager instance and sets the view controller as its delegate. This allows the view controller to receive updates from the location manager, such as authorization changes.
  3. The code then requests When In Use authorization by calling requestWhenInUseAuthorization() on the location manager. This prompts the user to grant or deny location permission.
  4. The code implements the CLLocationManagerDelegate protocol to handle authorization changes. The locationManagerDidChangeAuthorization(_:) method is called whenever the authorization status changes. The switch statement handles each possible authorization status and prints a corresponding message to the console.

Request Location Always Allow

we can retrieve user location information when application in foreground and also in background. To obtain Always authorization, our app must first request When In Use permission followed by requesting Always authorization.

We can call method requestAlwaysAuthorization() to ask Always authorization in locationManagerDidChangeAuthorization

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
print("When user did not yet determined")
case .restricted:
print("Restricted by parental control")
case .denied:
print("When user select option Dont't Allow")
// 1
case .authorizedAlways:
print("When user select option Change to Always Allow")
case .authorizedWhenInUse:
print("When user select option Allow While Using App or Allow Once")
// 2
locationManager?.requestAlwaysAuthorization()
default:
print("default")
}
}

Code explantion

  1. To retrieve the authorization status when the user has accepted Always Allow permission, we use the .authorizedAlways option.
  2. After the user accepts When In Use permission, we can then request permission for Always Allow authorization.
Asking Always Allow Permision after we got authorizedWhenInUse

Request One Time Location Information

If we want to retrieve a single location update, we can use the requestLocation() method of the locationManager. This method triggers a one-time location update and returns the location data to the CLLocationManagerDelegate method locationManager(_:didUpdateLocations:)

class ViewController: UIViewController {

var locationManager: CLLocationManager?

override func viewDidLoad() {
super.viewDidLoad()

locationManager = CLLocationManager()
locationManager?.delegate = self

locationManager?.requestWhenInUseAuthorization()

requestOnTimeLocation()
}

// 1
private func requestOnTimeLocation() {
locationManager?.requestLocation()
}
}

extension ViewController: CLLocationManagerDelegate {
// 2
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
print("Latitude: \(location.coordinate.latitude), Longitude: \(location.coordinate.longitude)")
}
}

Code explanation

  1. We call requestLocation() to get one time location information
  2. We implement method locationManager(_:didUpdateLocations:) in our view controller to get location information, since it only one-time location, we will get the last item from an array of locations

Continuous Location Updates

We are also able to keep getting user location updates from the user device, we can use the startUpdatingLocation() method of the CLLocationManager class. This method triggers a series of location updates, and the location data is returned to the CLLocationManagerDelegate method locationManager(_:didUpdateLocations:) each time the device's location changes. If you have done, you can call stopLocationUpdate

class ViewController: UIViewController {

var locationManager: CLLocationManager?

override func viewDidLoad() {
super.viewDidLoad()

locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.allowsBackgroundLocationUpdates = true
locationManager?.showsBackgroundLocationIndicator = true

locationManager?.requestWhenInUseAuthorization()

requestLocationUpdate()
}

// 1
private func requestLocationUpdate() {
locationManager?.startUpdatingLocation()
}

// 2
private func stopLocationUpdate() {
locationManager?.stopUpdatingLocation()
}
}

extension ViewController: CLLocationManagerDelegate {
// 3
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
print("Latitude: \(location.coordinate.latitude), Longitude: \(location.coordinate.longitude)")
}
}

From Apple Doc:
The system doesn’t automatically deliver location updates when an app enters the background. Instead, you must programmatically enable the delivery of those updates:

Set the allowsBackgroundLocationUpdates property of your location manager to true to enable background updates.

Set the showsBackgroundLocationIndicator property to true if your app has Always access to let people know when you’re using location services in the background.

It is important to be mindful of battery life when using continuous location updates. Continuous updates can drain the battery quickly, especially if the app is running in the background. Consider using significant location changes or region monitoring instead if continuous updates are not necessary for your app’s functionality.

Core Location Error Handling

When using Core Location, it is important to handle any errors that may occur, if you do not implement this method, Core Location throws an exception when attempting to use location services.

To handle errors in Core Location, you can use the CLLocationManagerDelegate protocol. This protocol provides methods that are called when an error occurs, such as locationManager(_:didFailWithError:).

extension ViewController: CLLocationManagerDelegate { 
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()

if let clErr = error as? CLError {
switch clErr.code {
case .locationUnknown, .denied, .network:
print("Location request failed with error: \(clErr.localizedDescription)")
case .headingFailure:
print("Heading request failed with error: \(clErr.localizedDescription)")
case .rangingUnavailable, .rangingFailure:
print("Ranging request failed with error: \(clErr.localizedDescription)")
case .regionMonitoringDenied, .regionMonitoringFailure, .regionMonitoringSetupDelayed, .regionMonitoringResponseDelayed:
print("Region monitoring request failed with error: \(clErr.localizedDescription)")
default:
print("Unknown location manager error: \(clErr.localizedDescription)")
}
} else {
print("Unknown error occurred while handling location manager error: \(error.localizedDescription)")
}
}
}

It’s not strictly required to include a call to stopUpdatingLocation() within the locationManager(_:didFailWithError:) function, but it is a recommended practice. The reason for calling stopUpdatingLocation() is to save battery power and avoid using location services when they are not necessary.

If there is an error while trying to update the user’s location, it is possible that subsequent attempts to update the location may also fail, leading to extra battery usage.

Debugging Core Location in Simulator

Debugging in the simulator help to identify issues in your app’s Core Location functionality.

Custom Static Location

We can specify coordinate for our simulator in the Simulator menu you can select menu Features -> Location -> Custom Location,

If we run our application, after the user gives permission, we can see that in the console it will be printed latitude and longitude from a custom location that we have defined before

Latitude: 37.785834, Longitude: -122.406417

Predefined movement

If we want to develop app that need location movement we also able to test it in simulator, in this case we want to test request for continuous location updates. iOS Simulator provide 3 options for predefined movement City Run, City Bicycle Ride, and Freeway Drive. To access these options, go to Features > Location in the simulator menu.

When running an iOS app and using the requestLocationUpdate method, the console will display continuous updates of the device's latitude and longitude

Latitude: 37.33058072, Longitude: -122.02891659
Latitude: 37.33056973, Longitude: -122.0288791
Latitude: 37.33055626, Longitude: -122.02884282
Latitude: 37.33053506, Longitude: -122.0288101
Latitude: 37.33051554, Longitude: -122.02877508

Conclusion

Core Location is a powerful framework that can be used to obtain a device’s location, altitude, and orientation. It collects data using all available components on the device such as Wi-Fi, GPS, Bluetooth, magnetometer, barometer, and cellular hardware. Core Location also has interesting features such as geofencing and region monitoring, which can be utilized to solve user problems in many ways. However, since location data is sensitive information, we need to request permission from the user before retrieving it.

We can request permission by using the CLLocationManager class and specifying whether we want to request permission for When In Use or Always Allow authorization. By setting up our project properly and requesting permission from the user, we can retrieve the user’s location information and create features based on their location.

--

--

Dwi Randy Herdinanto

A software developer that enthusiastic about mobile applications