Getting a good result from Core Location in a reasonable amount of time takes some careful finagling.
The issue is that once you start updating, didUpdateToLocation
will fire several times. Each time it fires, the location should be more accurate. However, you do not know how many times it will fire, nor do you know how fast a lock to your requested accuracy will occur (if it ever does). As per the documentation, additional events are generated if the minimum threshold distance (as specified by the distanceFilter
property) is exceeded or a more accurate location value is determined. Changing the distanceFilter
won't give you a better location (unless, of course, you're interested in precise movements).
There are some common setups to help make this simpler, but you'll probably want to fiddle with this depending upon the specific heuristics you desire.
- Set a timer after you send
startUpdating
to call a selector (can be as simple as performSelector:afterDelay:
). Set your delay to the maximum amount of time you're willing to wait for a reasonable approximation. In that selector's method, I would check to make sure that the location is accurate enough for placement in the database, and insert it. If it isn't, I'd alert the user.
- In
didUpdateToLocation
, immediately discard any locations that are too old. The LocationManager will often first return the cached value of its location -- and it may be very old.
- In
didUpdateToLocation
, simply save the latest result to an instance variable if it's not to your desired accuracy.
- In
didUpdateToLocation
, if the new location is accurate enough, cancel that perform selector after delay, and instead call it immediately.
That's just a rough sketch, but it's a pretty versatile setup that you can easily extend to have the functionality you desire.
Here's the basic outline of how this might work. This is a paired down snippet from one of my view controllers to just show the basics of the relevant parts:
- (void)viewWillAppear:(BOOL)animated {
[self.locationManager startUpdatingLocation];
[self performSelector:@selector(finishUpdating) withObject:nil afterDelay:10.0];
[super viewWillAppear:animated];
}
- (void) locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
/* Refuse updates more than a minute old */
if (abs([newLocation.timestamp timeIntervalSinceNow]) > 60.0) {
return;
}
/* Save the new location to an instance variable */
self.lastUpdatedLocation = newLocation;
/* If it's accurate enough, cancel the timer */
if (newLocation.horizontalAccuracy < 20.0) {
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(finishUpdating)
object:nil]
/* And fire it manually instead */
[self finishUpdating];
}
}
- (void) finishUpdating {
/* Check the accuracy of self.lastUpdatedLocation, and either
* save it or alert the user that it's not accurate enough */
[self.locationManager stopUpdatingLocation];
}