Location and Maps Programming Guide

Getting the User’s Location

Apps use location data for many purposes, ranging from social networking to turn-by-turn navigation services. They get location data through the classes of the Core Location framework. This framework provides several services that you can use to get and monitor the device’s current location:

The significant-change location service provides a low-power way to get the current location and be notified when significant changes occur.

The standard location service offers a highly configurable way to get the current location and track changes.

To use the features of the Core Location framework, you must link your app to CoreLocation.framework in your Xcode project. To access the classes and headers of the framework, include an #import <CoreLocation/CoreLocation.h> statement at the top of any relevant source files.

Requiring the Presence of Location Services in an iOS App

If your iOS app requires location services to function properly, include the UIRequiredDeviceCapabilities key in the app’s Info.plist file. The App Store uses the information in this key to prevent users from downloading apps to devices that don’t contain the listed features.

The value for the UIRequiredDeviceCapabilities is an array of strings indicating the features that your app requires. Two strings are relevant to location services:

Include the location-services string if you require location services in general.

Include the gps string if your app requires the accuracy offered only by GPS hardware.

Important: If your iOS app uses location services but is able to operate successfully without them, don’t include the corresponding strings in the UIRequiredDeviceCapabilities key.

Getting the User’s Current Location

The Core Location framework lets you locate the current position of the device and use that information in your app. The framework reports the device’s location to your code and, depending on how you configure the service, also provides periodic updates as it receives new or improved data.

Two services can give you the user’s current location:

The standard location service is a configurable, general-purpose solution for getting location data and tracking location changes for the specified level of accuracy.

The significant-change location service delivers updates only when there has been a significant change in the device’s location, such as 500 meters or more.

Gathering location data is a power-intensive operation. For most apps, it’s usually sufficient to establish an initial position fix and then acquire updates only periodically after that. Regardless of the importance of location data in your app, you should choose the appropriate location service and use it wisely to avoid draining a device’s battery. For example:

If your iOS app must keep monitoring location even while it’s in the background, use the standard location service and specify the location value of the UIBackgroundModes key to continue running in the background and receiving location updates. (In this situation, you should also make sure the location manager’s pausesLocationUpdatesAutomatically property is set to YES to help conserve power.) Examples of apps that might need this type of location updating are fitness or turn-by-turn navigation apps.

If GPS-level accuracy isn’t critical for your app and you don’t need continuous tracking, use the signicant-change location service. This service can save power by not tracking continuously and only waking the device and launching your app when significant changes occur.

Determining Whether Location Services Are Available

There are situations where location services may not be available. For example:

The user disables location services in the Settings app or System Preferences.

The user denies location services for a specific app.

The device is in Airplane mode and unable to power up the necessary hardware.

For these reasons, it’s recommended that you always call the locationServicesEnabled class method of CLLocationManager before attempting to start either the standard or significant-change location services. If it returns NO and you attempt to start location services anyway, the system prompts the user to confirm whether location services should be re-enabled. Because the user probably disabled location services on purpose, the prompt is likely to be unwelcome.

Starting the Standard Location Service

The standard location service is the most common way to get a user’s current location because it’s available on all devices and in both iOS and OS X. Before using this service, you configure it by specifying the desired accuracy of the location data and the distance that must be traveled before reporting a new location. When you start the service, it uses the specified parameters to determine the hardware to enable and then proceeds to report location events to your app. Because this service takes into account these parameters, it’s most appropriate for apps that need more fine-grained control over the delivery of location events. The precision of the standard location service is needed by navigation apps or any app that requires high-precision location data or a regular stream of updates. Because this service typically requires the location-tracking hardware to be enabled for longer periods of time, higher power usage can result.

To use the standard location service, create an instance of the CLLocationManager class and configure its desiredAccuracy and distanceFilter properties. To begin receiving location notifications, assign a delegate to the object and call the startUpdatingLocation method. As location data becomes available, the location manager notifies its assigned delegate object. If a location update has already been delivered, you can also get the most recent location data directly from the CLLocationManager object without waiting for a new event to be delivered. To stop the delivery of location updates, call the stopUpdatingLocation method of the location manager object.

Listing 1-1 shows a sample method that configures a location manager for use. The sample method is part of a class that caches its location manager object in a member variable for later use. (The class also conforms to the CLLocationManagerDelegateprotocol and so acts as the delegate for the location manager.) Because the app doesn’t need precise location data, it configures the location service to report the user’s general area and send notifications only when the user moves at least half a kilometer.

Starting the Significant-Change Location Service

The significant-change location service provides accuracy that’s good enough for most apps and represents a power-saving alternative to the standard location service. The service uses Wi-Fi to determine the user’s location and report changes in that location, allowing the system to manage power usage much more aggressively than it could otherwise. The significant-change location service can also wake up an iOS app that is currently suspended or not running in order to deliver new location data.

To use the significant-change location service, create an instance of the CLLocationManager class, assign a delegate to it, and call the startMonitoringSignificantLocationChanges method as shown in Listing 1-2. As location data becomes available, the location manager notifies its assigned delegate object. If a location update has already been delivered, you can also get the most recent location data directly from the CLLocationManager object without waiting for a new event to be delivered.

If you leave the significant-change location service running and your iOS app is subsequently suspended or terminated, the service automatically wakes up your app when new location data arrives. At wake-up time, the app is put into the background and you are given a small amount of time (around 10 seconds) to manually restart location services and process the location data. (You must manually restart location services in the background before any pending location updates can be delivered, as described in Knowing When to Start Location Services.) Because your app is in the background, it must do minimal work and avoid any tasks (such as querying the network) that might prevent it from returning before the allocated time expires. If it does not, your app will be terminated. If an iOS app needs more time to process the location data, it can request more background execution time using the beginBackgroundTaskWithName:expirationHandler: method of the UIApplication class.

Note: When a user disables the Background App Refresh setting either globally or for your app, the significant-change location service doesn’t relaunch your app. Further, while Background App Refresh is off an app doesn’t receive significant-change or region monitoring events even when it's in the foreground.

Receiving Location Data from a Service

The way you receive location events is the same whether you use the standard or the significant-change location service to get them. Beginning in OS X v10.9 and iOS 6, the location manager reports events to the locationManager:didUpdateLocations: method of its delegate when they become available. (In earlier versions of both operating systems, the location manager reports events to the locationManager:didUpdateToLocation:fromLocation: method.) If there is an error retrieving an event, the location manager calls the locationManager:didFailWithError: method of its delegate instead.

Listing 1-3 shows the delegate method for receiving location events. Because the location manager object sometimes returns cached events, it’s recommended that you check the timestamp of any location events you receive. (It can take several seconds to obtain a rough location fix, so the old data simply serves as a way to reflect the last known location.) In this example, the method throws away any events that are more than fifteen seconds old under the assumption that events up to that age are likely to be good enough. If you are implementing a navigation app, you might want to lower the threshold.

Listing 1-3 Processing an incoming location event

// Delegate method from the CLLocationManagerDelegate protocol.

- (void)locationManager:(CLLocationManager *)manager

didUpdateLocations:(NSArray *)locations {

// If it's a relatively recent event, turn off updates to save power.

CLLocation* location = [locations lastObject];

NSDate* eventDate = location.timestamp;

NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];

if (abs(howRecent) < 15.0) {

// If the event is recent, do something with it.

NSLog(@"latitude %+.6f, longitude %+.6f\n",

location.coordinate.latitude,

location.coordinate.longitude);

}

}

In addition to a location object’s timestamp, you can also use the accuracy reported by that object to determine whether you want to accept an event. As it receives more accurate data, the location service may return additional events, with the accuracy values reflecting the improvements accordingly. Throwing away less accurate events means your app wastes less time on events that can’t be used effectively anyway.

Knowing When to Start Location Services

Apps that use location services should not start those services until they’re needed. With a few exceptions, avoid starting location services immediately at launch time or before such services might reasonably be used. Otherwise you might raise questions in the user’s head about how your app uses location data. The user knows when your app starts location services because the first time your app starts the service, the system prompts the user for permission to use it. Waiting until the user performs a task that actually requires those services helps build trust that your app is using them appropriately. Another way to build trust is to include the NSLocationUsageDescription key in your app’s Info.plist file and set the value of that key to a string that describes how your app intends to use location data.

If you are monitoring regions or using the significant-change location service in your app, there are situations where you must start location services at launch time. Apps using those services can be terminated and subsequently relaunched when new location events arrive. Although the app itself is relaunched, location services are not started automatically. When an app is relaunched because of a location update, the launch options dictionary passed to your application:willFinishLaunchingWithOptions: or application:didFinishLaunchingWithOptions: method contains the UIApplicationLaunchOptionsLocationKey key. The presence of that key signals that new location data is waiting to be delivered to your app. To obtain that data, you must create a new CLLocationManager object and restart the location services that you had running prior to your app’s termination. When you restart those services, the location manager delivers all pending location updates to its delegate.

Getting Location Events in the Background (iOS Only)

iOS supports the delivery of location events to apps that are suspended or no longer running. The delivery of location events in the background supports apps whose functionality would be impaired without them, so configure your app to receive background events only when doing so provides a tangible benefit to the user. For example, a turn-by-turn navigation app needs to track the user’s position at all times and notify the user when it’s time to make the next turn. If your app can make do with alternate means, such as region monitoring, it should do so.

You have multiple options for obtaining background location events, and each has advantages and disadvantages with regard to power consumption and location accuracy. Whenever possible, apps should use the significant-change location service (described in Starting the Significant-Change Location Service), which can use Wi-Fi to determine the user’s position and consumes the least amount of power. But if your app requires higher precision location data, you can configure it as a background location app and use the standard location service.

Important: A user can explicitly disable background capabilities for any app. If a user disables Background App Refresh in the Settings app—either globally for all apps or for your app in particular—your app is prevented from using any location services in the background. You can determine whether your app can process location updates in the background by checking the value of the backgroundRefreshStatus property of the UIApplication class.

Using the Standard Location Service in the Background

iOS apps can use the standard location service in the background if they provide services that require continuous location updates. As a result, this capability is most appropriate for apps that assist the user in navigation- and fitness-related activities. To enable this capability in your app, enable the Background Modes capability in your Xcode project (located in the Capabilities tab of your project) and enable the Location updates mode. The code you write to start and stop the standard location services is unchanged.

The system delivers location updates to background location apps when the app is in the foreground, is running in the background, or is suspended. In the case of a suspended app, the system wakes up the app, delivers the update to the location manager’s delegate, and then returns the app to the suspended state as soon as possible. While it is running in the background, your app should do as little work as possible to process the new location data.

When location services are enabled, iOS must keep the location hardware powered up so that it can gather new data. Keeping the location hardware running degrades battery life, and you should always stop location services in your app whenever you do not need the resulting location data.

If you must use location services in the background, you can help Core Location maintain good battery life for the user’s device by taking additional steps when you configure your location manager object:

Make sure the location manager’s pausesLocationUpdatesAutomatically property is set to YES. When this property is set to YES, Core Location pauses location updates (and powers down the location hardware) whenever it makes sense to do so, such as when the user is unlikely to be moving anyway. (Core Location also pauses updates when it can’t obtain a location fix.)

Assign an appropriate value to the location manager’s activityType property. The value in this property helps the location manager determine when it is safe to pause location updates. For an app that provides turn-by-turn automobile navigation, setting the property to CLActivityTypeAutomotiveNavigation causes the location manager to pause events only when the user does not move a significant distance over a period of time.

When the location manager pauses location updates, it notifies its delegate object by calling its locationManagerDidPauseLocationUpdates: method. When the location manager resumes updates, it calls the delegate’s locationManagerDidResumeLocationUpdates: method. You can use these delegate methods to perform tasks or adjust the behavior of your app. For example, when location updates are paused, you might use the delegate notification to save data to disk or stop location updates altogether. A navigation app in the middle of turn-by-turn directions might prompt the user and ask whether navigation should be disabled temporarily.

Note: If your app is terminated either by a user or by the system, the system doesn’t automatically restart your app when new location updates arrive. A user must explicitly relaunch your app before the delivery of location updates resumes. The only way to have your app relaunched automatically is to use region monitoring or the significant-change location service.

However, when a user disables the Background App Refresh setting either globally or specifically for your app, the system doesn’t relaunch your app for any location events, including significant-change or region monitoring events. Further, while Background App Refresh is off your app won’t receive significant-change or region monitoring events even when it's in the foreground. When a user reenables Background App Refresh for your app, Core Location restores all background services, including any previously registered regions.

Deferring Location Updates While Your App Is in the Background

In iOS 6 and later, you can defer the delivery of location updates when your app is in the background. It’s recommended that you use this feature when your app could process the location data later without any problems. For example, a fitness app that tracks the user’s location on a hiking trail could defer updates until the user hikes a certain distance or until a certain amount of time has elapsed and then process the updates all at once. Deferring updates saves power by letting your app sleep for longer periods of time. Because deferring location updates requires the presence of GPS hardware on the target device, be sure to call the deferredLocationUpdatesAvailable class method of the CLLocationManager class to determine whether the device supports deferred location updates.

Call the allowDeferredLocationUpdatesUntilTraveled:timeout: method of the CLLocationManager class to begin deferring location updates. As shown in Listing 1-4, the usual place to call this method is in the locationManager:didUpdateLocations: method of your location manager delegate object. This method shows how a sample hiking app might defer location updates until the user hikes a minimum distance. To prevent itself from calling the allowDeferredLocationUpdatesUntilTraveled:timeout: method multiple times, the delegate uses an internal property to track whether updates have already been deferred. It sets the property to YES in this method and sets it back to NO when deferred updates end.

Listing 1-4 Deferring location updates

// Delegate method from the CLLocationManagerDelegate protocol.

- (void)locationManager:(CLLocationManager *)manager

didUpdateLocations:(NSArray *)locations {

// Add the new locations to the hike

[self.hike addLocations:locations];

// Defer updates until the user hikes a certain distance

// or when a certain amount of time has passed.

if (!self.deferringUpdates) {

CLLocationDistance distance = self.hike.goal - self.hike.distance;

NSTimeInterval time = [self.nextAudible timeIntervalSinceNow];

[locationManager allowDeferredLocationUpdatesUntilTraveled:distance

timeout:time];

self.deferringUpdates = YES;

}

}

When a condition you specified in the allowDeferredLocationUpdatesUntilTraveled:timeout: method is met, the location manager calls the locationManager:didFinishDeferredUpdatesWithError: method of its delegate object to let you know that it has stopped deferring the delivery of location updates. The location manager calls this delegate method exactly once for each time your app calls the allowDeferredLocationUpdatesUntilTraveled:timeout: method. After deferred updates end, the location manager proceeds to deliver any location updates to your delegate’s locationManager:didUpdateLocations: method.

You can stop the deferral of location updates explicitly by calling the disallowDeferredLocationUpdates method of the CLLocationManager class. When you call this method, or stop location updates altogether using the stopUpdatingLocation method, the location manager calls your delegate’s locationManager:didFinishDeferredUpdatesWithError: method to let you know that deferred updates have indeed stopped.

If the location manager encounters an error and can’t defer location updates, you can access the cause of the error when you implement the locationManager:didFinishDeferredUpdatesWithError: delegate method. For a list of the possible errors that may be returned—and what, if anything, you can to resolve them—see the CLError constants in Core Location Constants Reference.

Tips for Conserving Battery Power

Getting location data in an iOS-based device can require a lot of power. Because most apps don’t need location services to be running all the time, turning off location services when they’re not needed is the simplest way to save power.

Turn off location services when you aren’t using them. This guideline may seem obvious but it’s worth repeating. With the exception of navigation apps that offer turn-by-turn directions, most apps don’t need location services to be on all the time. Turn location services on just long enough to get a location fix and then turn them off. Unless the user is in a moving vehicle, the current location shouldn’t change frequently enough to be an issue. And you can always start location services again later if needed.

Use the significant-change location service instead of the standard location service whenever possible. The significant-change location service provides significant power savings while still allowing you to leave location services running. This is highly recommended for apps that need to track changes in the user’s location but don’t need the higher precision offered by the standard location services.

Use lower-resolution values for the desired accuracy unless doing so would impair your app. Requesting a higher accuracy than you need causes Core Location to power up additional hardware and waste power for precision you don’t need. Unless your app really needs to know the user’s position within a few meters, don’t put the values kCLLocationAccuracyBest or kCLLocationAccuracyNearestTenMeters in the desiredAccuracy property. And remember that specifying a value of kCLLocationAccuracyThreeKilometers doesn’t prevent the location service from returning better data. Most of the time, Core Location can return location data with an accuracy within a hundred meters or so.

Turn off location events if the accuracy doesn’t improve over a period of time. If your app isn’t receiving events with the desired level of accuracy, you should look at the accuracy of events you do receive and see if it is improving or staying about the same over time. If accuracy isn’t improving, it could be because the desired accuracy is simply not available at the moment. Turning off location services and trying again later prevents your app from wasting power.

Specify an activity type for your app when using the standard location service to process location data in the background. Letting Core Location know what type of activity is associated with your app (for example, whether it’s an automobile navigation app or a fitness app) helps the location manager determine the most appropriate time to pause location updates when your app is in the background. Helping the location manager to determine when to pause location updates in your app can help improve battery life on the user’s device.

Allow the location manager to defer the delivery of location updates when your app is in the background. When your app can’t do anything useful with the location updates it receives from the location manager—other than log the information and go back to sleep—allow the location manager to defer those updates until they’re more meaningful to your app.