Common issues when using Core Location framework on iOS 8
Using the Core Location framework properly and efficiently is a must for those developing iOS apps that make use of the user’s location. I am writing this post to cover some following typical challenges when using Core Location on iOS 8 to help others with similar issues.
Working with Core Location on iOS8
If you are upgrading from iOS 7 to iOS8, it may take you a few minutes (or even a few hours) to find out the reason why your code doesn’t work at all. You may discover that the locationManager:didChangeAuthorizationStatus, locationManager:didUpdateToLocation:newLocation fromLocation and locationManager:didUpdateLocations are not called at all. Why?
First we should look at the Apple’s release notes for CLLocationManager:
Added -[CLLocationManager requestAlwaysAuthorization] Added -[CLLocationManager requestWhenInUseAuthorization] Added kCLAuthorizationStatusAuthorizedAlways Added kCLAuthorizationStatusAuthorizedWhenInUse
According to the release notes, you’ll need to do a few things to make your location-aware app compatible with iOS 8. First, add one or both of the following keys to your Info.plist file:
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
Remember to provide a meaningful message describing the purpose of obtaining the user’s location; otherwise your app may be rejected when submitting to the App Store.
Secondly, you have to call the requestAlwaysAuthorization or the requestWhenInUseAuthorization
method prior to using location services.
After modification, [locationManager startUpdateLocation] should work normally.
But that’s not all. Next, we should carefully go through the list of changes in the release notes for Core Location to adjust our code to work perfectly with iOS8. It’s good practice!
Handling duplicate LocationManager delegate callbacks
LocationManager returns the user’s location via the locationManager:didUpdateToLocation:fromLocation or locationManager:didUpdateLocations callback. You will sometimes wonder that why the callbacks get called many times, or why the first location obtained is usually quite “old”.
To understand the reason, we should take a look at what happens “behind the scene” of CLLocationManager where location data is acquired. Apple documentation states:
Calculating a phone’s location using just GPS satellite data can take up to several minutes. iPhone can reduce this time to just a few seconds by using Wi-Fi hotspot and cell tower data to quickly find GPS satellites.
CLLocationManager tries to provide you with a location as soon as possible so that you can process your logic without waiting for a few seconds. To do that, it caches location data from the last time you call [locationManager startUpdatingLocation].
To verify this point, you can try uninstalling your app, turn off master location service, then re-install your app, and launch it. You will see 2 things: (1)it takes a few seconds before calling back locationManager:didUpdateToLocation:fromLocation:, and (2) only one time invocation of locationManager:didUpdateToLocation:fromLocation:
Now, terminate your app, wait a bit, and re-launch it. You will see 2 different things:
- the callback is called almost immediately (with cached data)
- the callback is called twice or more (depends on your waiting time and desired accuracy)
To avoid receiving old data, you have to check the “age” of the location you receive and its accuracy before making use of the location. Here is an example code:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ NSTimeInterval locationAge = [[(CLLocation *)[locations lastObject] timestamp] timeIntervalSinceNow]; NSLog(@"how old the location is: %.5f", locationAge); }
Using mock location
The location reported to your app can be simulated by enabling the location simulation feature in Xcode and run your app from xCode:
Your app will then receive the simulated location and act as if this is the real location of the user, making it easy to test the app during development.
However, what is worth mentioning is that, once the fake location has been set for the device, future apps that run on the same device will also receive the simulated location as the user’s current location, which is “Tokyo, Japan” as configured above, regardless of the actual location of the device. The real location is only reported again after the device has been rebooted or the app under development uninstalled. This seems to be a serious security risk on iOS 8.
So, is there any way to detect this behavior and prevent it?
To detect the simulated location, we need to understand its behavior. The simulated location behavior is quite different from the real one at call back locationManager:didUpdateLocations:
For simulated locations, the callback returns almost immediately after calling startUpdatingLocation, and will be repeatedly called every exactly one second, with exactly the same location coordinates:
location: <+51.50998000,-0.13370000> +/- 5.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:12:48 location: <+51.50998000,-0.13370000> +/- 5.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:12:49 location: <+51.50998000,-0.13370000> +/- 5.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:12:50 location: <+51.50998000,-0.13370000> +/- 5.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:12:51 location: <+51.50998000,-0.13370000> +/- 5.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:12:52 location: <+51.50998000,-0.13370000> +/- 5.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:12:53 location: <+51.50998000,-0.13370000> +/- 5.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:12:54
For real locations, the callback will most likely take a few seconds to return and will be recalled randomly thereafter. There will also be significant differences among the reportedlocations even with no changes in the device’s location::
location: <+10.77219361,+106.70597441> +/- 67.39m (speed -1.00 mps / course -1.00) @ 30.03.15 14:16:26 location: <+10.77213011,+106.70591088> +/- 65.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:16:31 location: <+10.77219507,+106.70587790> +/- 65.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:16:38 location: <+10.77214753,+106.70587741> +/- 65.00m (speed -1.00 mps / course -1.00) @ 30.03.15 14:16:49
To cater for this, I just work around for now by making at least 3 attempts to look up the user’s locations, and checking the lat/long/accuracy/speed/and course value to determine if it’s simulated or real location. This is just a temporary measure as Apple may change the behaviour in the future.
By the way, I’ve also tried to disallow location simulation on xCode at the Scheme settings:
Unfortunately, it still allows simulated locations even with the setting unchecked. This may be an xCode bug – let’s see if it will be fixed in future releases of the iOS SDK.