When iOS 6 launched last year, Apple deprecated a commonly used method from the CLLocationManagerDelegate protocol. The method deprecated is – (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation and is used to tell the delegate that a new location is available. Each time a location change is detected, this method is called and within the method, you can trigger certain methods or updates to happen as needed.
This was done by using the (CLLocation *)newLocation details. With this method being deprecated in iOS 6, Apple recommends you now use a newer method called – (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations. Rather than providing a newLocation as a CLLocation, this new method returns an NSArray of locations. As the value is a different type, you will need to modify your code to account for the data being handed back in an NSArray.
The good news is that according to the documentation, you still get a CLLocation object, but instead of accessing it direct, you need to pull it out of the NSArray to action it. Lets take a look at some code that will help you do just that.
Pulling CLLocation data from the locationManager:didUpdateLocations: Method
Looking at the sample code below, taken from the Regions sample Xcode project from the Apple Developer site, we see the newLocation CLLocation being used as below:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSLog(@"didUpdateToLocation %@ from %@", newLocation, oldLocation);
MKCoordinateRegion userLocation = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 1500.0, 1500.0);
[regionsMapView setRegion:userLocation animated:YES];
}
Line 1 is the instance method. Line 2 us used to NSLog the data. In this case, we are simply using %@ and NSLogging the newLocation (as well as the oldLocation). On line 3, we create an MKCoordinateRegion by accessing the coordinate property of newLocation and then on line 4, we set the region.
Overall, a very simple few lines of code.
Implementing the new iOS 6 method, locationManager:didUpdateLocations: we would simply need to access the lastObject of the NSArray to get the CLLocation. We can do this as follows:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *newLocation = [locations lastObject];
CLLocation *oldLocation;
if (locations.count > 1) {
oldLocation = [locations objectAtIndex:locations.count-2];
} else {
oldLocation = nil;
}
NSLog(@"didUpdateToLocation %@ from %@", newLocation, oldLocation);
MKCoordinateRegion userLocation = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 1500.0, 1500.0);
[regionsMapView setRegion:userLocation animated:YES];
}
To keep a lot of the code the same (lines 9, 10 and 11), we can simply create a local CLLocation called newLocation and another called oldLocation and pull out the information from the NSArray. On line 2, we access the lastObject of the NSArray as the last object contains the most recent update. To access the previous update, we could technically do what is done on line 5 and use locations.count-2 to access the previous object. However, there is a problem with doing this as the NSArray might not necessarily contain the previous location. So what we need to do before that is check the size of the NSArray. If it contains a single object, just set oldLocation to nil. If it does contain more than 1 object then store that in oldLocation.
The reason we do a -2 is because (as pointed out in the comments by Sam), when we get the NSArray count, we are counting total objects and need to go back 2 to get to the previous object.
Why the Switch from CLLocation to an NSArray of CLLocation(s)?
The CLLocationManagerDelegate Protocol documentation indicates that several updates might be detected but deferred and then delivered at a later date. By using an NSArray, these changes are stacked up in the NSArray which can then be grabbed as needed. Usually, there is just the latest CLLocation found within the locations NSArray, but sometimes, it could be more. I suspect it was done this way so that events were not lost and could still be actioned if needed. With the old method, that simply tracked the newLocation and oldLocation and none others.
As can be seen, the changes are fairly minor for this simple bit of code. Your own code will likely be more complex depending on what your app utilises CoreLocation for, but you still can grab the information you need from accessing the lastObject on the NSArray. If you are still building for iOS 5 or below, then you need to carry on using the old method. If you build for iOS 6 only, I suggest switching to the new method.
John legg says
Thank you for a quick informative tutorial. Is there a way to use Both methods to support existing users(iOS5) as well as newer users(iOS6)? If so, could you provide the example code/
thank you,
John
MatthewN says
Hello John,
I’d probably do it the following way whereby I have both the new and the deprecated method and then call the new method from the old one as follows:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *newLocation = [locations lastObject];
//All code goes in this method.
NSLog(@”Update %@”, newLocation.timestamp);
}
– (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSArray *currentLocation = [[NSArray alloc] initWithObjects:newLocation, nil];
[self locationManager:locationManager didUpdateLocations:currentLocation];
}
Put all your code in the new method. If iOS 5 is the running OS on the device, the old method will be called and newLocation would be added to an NSArray and then passed to the new method. If iOS 6 is running then the old method will be ignored and the new method would be run by default.
To slim the older method down, you could just alloc/init within the method call as follows:
[self locationManager:locationManager didUpdateLocations:[[NSArray alloc] initWithObjects:newLocation, nil]];
I’ll probably do a post on this tonight as it’s a good question and worthy of more explanation.
Sam says
Good article, with exception of one detail. Getting the old location doesn’t work up there.
CLLocation *oldLocation = [locations objectAtIndex:locations.count-1];
The above line of code just retrieves the last object in the array. Say the array had 10 objects starting with 0. That means the index of the last object is 9. So when you call [locations lastObject] you end up with the object at the ninth index. But if you ask for the locations.count property – you’ll get 10 because they’re ten objects. As a result, you’ll end up with the ninth index (or the last object) when you use the line of code above.
It would work better if you changed it to 2 instead of 1.
MatthewN says
Well spotted! I’ll update the tutorial with the change. I guess some sort of error checking will be needed because if just 1 object exists in the array then the objectAtIndex will be -1 which would cause a problem.
Temitope says
thanks, helpful :D