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
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>
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
- The first line of the code imports the Core Location framework, which provides the classes for working with location data.
- In the
viewDidLoad()
method, the code creates aCLLocationManager
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. - The code then requests
When In Use
authorization by callingrequestWhenInUseAuthorization()
on the location manager. This prompts the user to grant or deny location permission. - The code implements the
CLLocationManagerDelegate
protocol to handle authorization changes. ThelocationManagerDidChangeAuthorization(_:)
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
- To retrieve the authorization status when the user has accepted
Always Allow
permission, we use the.authorizedAlways
option. - After the user accepts
When In Use
permission, we can then request permission forAlways Allow
authorization.
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
- We call
requestLocation()
to get one time location information - 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.