iOS devices can employ several techniques for obtaining information about the current geographical location of the device. These mechanisms include GPS, cell tower triangulation, and finally (and least accurately), using the IP address of available Wi-Fi connections. The mechanism used by iOS to detect location information is largely transparent to the app developer. The system will automatically use the most accurate solution available at any given time. All that is needed to integrate location-based information into an iOS app is understanding how to use the Core Location Framework, which is the subject of this chapter.
Once the basics of location tracking with Core Location have been covered in this chapter, the next chapter will provide detailed steps on how to create An Example iOS 17 Location App.
The Core Location Manager
The key classes contained within the Core Location Framework are CLLocationManager and CLLocation. An instance of the CLLocationManager class can be created using the following Swift code:
var locationManager: CLLocationManager = CLLocationManager()
Once a location manager instance has been created, it must seek permission from the user to collect location information before it can begin to track data.
Requesting Location Access Authorization
Before any app can begin tracking location data, it must first seek permission from the user. This can be achieved by calling one of two methods on the CLLocationManager instance, depending on the specific requirement. For example, suppose the app only needs to track location information when the app is in the foreground. In that case, a call should be made to the requestWhenInUseAuthorization method of the location manager instance. For example:
locationManager.requestWhenInUseAuthorization()
If tracking is also required when the app is running in the background, the requestAlwaysAuthorization method should be called:
locationManager.requestAlwaysAuthorization()
If an app requires always authorization, the recommended path to requesting this permission is first to seek when in use permission and then offer the user the opportunity to elevate this permission to always mode at the point that the app needs it. The reasoning behind this recommendation is that when seeking always permission, the request dialog displayed by iOS will provide the user the option of using either when in use or always location tracking. Given these choices, most users will typically select the when in use option. Therefore, a better approach is to begin by requesting when in use tracking and then explain the benefits of elevating to always mode in a later request.
Both location authorization request method calls require that specific key-value pairs be added to the Information Property List dictionary contained within the app’s Info.plist file. The values take the form of strings and must describe the reason why the app needs access to the user’s current location. The keys associated with these values are as follows:
- NSLocationWhenInUseUsageDescription – A string value describing to the user why the app needs access to the current location when running in the foreground. This string is displayed when a call is made to the requestWhenInUseAuthorization method of the locationManager instance. The dialog displayed to the user containing this message will only provide the option to permit when in use location tracking. All apps built using the iOS 11 SDK or later must include this key regardless of the usage permission level being requested to access the device location.
- NSLocationAlwaysAndWhenInUseUsageDesciption – The string displayed when permission is requested for always authorization using the requestAlwaysAuthorization method. The request dialog containing this message will allow the user to select either always or when in use authorization. All apps built using the iOS 11 SDK or later must include this key when accessing device location information.
- NSLocationAlwaysUsageDescription – A string describing to the user why the app needs always access to the current location. This description is not used on devices running iOS 11 or later, though it should still be declared for compatibility with legacy devices.
Configuring the Desired Location Accuracy
The level of accuracy to which location information is to be tracked is specified via the desiredAccuracy property of the CLLocationManager object. It is important to keep in mind when configuring this property that the greater the level of accuracy selected, the greater the drain on the device battery. An app should, therefore, never request a greater accuracy than is needed.
Several predefined constant values are available for use when configuring this property:
- kCLLocationAccuracyBestForNavigation – Uses the highest possible level of accuracy augmented by additional sensor data. This accuracy level is intended solely for when the device is connected to an external power supply.
- kCLLocationAccuracyBest – The highest recommended level of accuracy for devices running on battery power.
- kCLLocationAccuracyNearestTenMeters – Accurate to within 10 meters.
- kCLLocationAccuracyHundredMeters – Accurate to within 100 meters.
- kCLLocationAccuracyKilometer – Accurate to within one kilometer.
- kCLLocationAccuracyThreeKilometers – Accurate to within three kilometers.
The following code, for example, sets the level of accuracy for a location manager instance to “best accuracy”:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
Configuring the Distance Filter
The default configuration for the location manager is to report updates whenever any changes are detected in the device’s location. The distanceFilter property of the location manager allows apps to specify the amount of distance the device location must change before an update is triggered. If, for example, the distance filter is set to 1000 meters, the app will only receive a location update when the device travels 1000 meters or more from the location of the last update. For example, to specify a distance filter of 1500 meters:
locationManager.distanceFilter = 1500.0
The distance filter may be canceled, thereby returning to the default setting, using the kCLDistanceFilterNone constant:
locationManager.distanceFilter = kCLDistanceFilterNone
Continuous Background Location Updates
The location tracking options covered so far in this chapter only receive updates when the app is either in the foreground or background. The updates will stop as soon as the app enters the suspended state (in other words, the app is still resident in memory but is no longer executing code). However, if location updates are required even when the app is suspended (a key requirement for navigation-based apps), continuous background location updates must be enabled for the app. When enabled, the app will be woken from suspension each time a location update is triggered and provided the latest location data.
Enable continuous location updates is a two-step process beginning with the addition of an entry to the project Info.plist file. This is most easily achieved by enabling the location updates background mode in the Xcode Signing & Capabilities panel, as shown in Figure 66-1:
Within the app code, continuous updates are enabled by setting the allowsBackgroundLocationUpdates property of the location manager to true:
locationManager.allowsBackgroundLocationUpdates = true
To allow the location manager to suspend updates temporarily, set the pausesLocationUpdatesAutomatically property of the location manager to true.
locationManager.pausesLocationUpdatesAutomatically = true
This setting allows the location manager to extend battery life by pausing updates when it is appropriate to do so (for example, when the user’s location remains unchanged for a significant amount of time). When the user starts moving again, the location manager will automatically resume updates.
Continuous location background updates are available for apps for both always and when in use authorization modes.
The Location Manager Delegate
Location manager updates and errors result in calls to two delegate methods defined within the CLLocationManagerDelegate protocol. Templates for the two delegate methods that must be implemented to comply with this protocol are as follows:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { // Handle location updates here } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { // Handle errors here }
Each time the location changes, the didUpdateLocations delegate method is called and passed as an argument an array of CLLocation objects with the last object in the array containing the most recent location data.
Changes to the location tracking authorization status of an app are reported via a call to the optional didChangeAuthorization delegate method:
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { // App may no longer be authorized to obtain location //information. Check the status here and respond accordingly. }
Once a class has been configured to act as the delegate for the location manager, that object must be assigned to the location manager instance. In most cases, the delegate will be the same view controller class in which the location manager resides, for example:
locationManager.delegate = self
Starting and Stopping Location Updates
Once suitably configured and authorized, the location manager can then be instructed to start tracking location information:
locationManager.startUpdatingLocation()
With each location update, the didUpdateLocations delegate method is called by the location manager and passed information about the current location.
To stop location updates, call the stopUdatingLocation method of the location manager as follows:
locationManager.stopUpdatingLocation()
Obtaining Location Information from CLLocation Objects
Location information is passed through to the didUpdateLocation delegate method in the form of CLLocation objects. A CLLocation object encapsulates the following data:
- Latitude
- Longitude
- Horizontal Accuracy
- Altitude
- Altitude Accuracy
Longitude and Latitude
Longitude and latitude values are stored as type CLLocationDegrees and may be obtained from a CLLocation object as follows:
let currentLatitude: CLLocationDistance = location.coordinate.latitude let currentLongitude: CLLocationDistance = location.coordinate.longitude
Accuracy
Horizontal and vertical accuracy are stored in meters as CLLocationAccuracy values and may be accessed as follows:
let verticalAccuracy: CLLocationAccuracy = location.verticalAccuracy let horizontalAccuracy: CLLocationAccuracy = location.horizontalAccuracy
Altitude
The altitude value is stored in meters as a type CLLocationDistance value and may be accessed from a CLLocation object as follows:
let altitude: CLLocationDistance = location.altitude
Getting the Current Location
If all that is required from the location manager is the user’s current location without the need for continuous location updates, this can be achieved via a call to the requestLocation method of the location manager instance. This method will identify the current location and call the didUpdateLocations delegate once passing through the current location information. Location updates are then automatically turned off:
locationManager.requestLocation()
Calculating Distances
The distance between two CLLocation points may be calculated by calling the distance(from:) method of the end location and passing through the start location as an argument. For example, the following code calculates the distance between the points specified by startLocation and endLocation:
var distance: CLLocationDistance = endLocation.distance(from: startLocation)
Summary
This chapter has provided an overview of the use of the iOS Core Location Framework to obtain location information within an iOS app. This theory will be put into practice in the next chapter entitled An Example iOS 17 Location App.