I've had lots of issues with CLLocationManager on the iPhone also.
From different Apps, and trial and error this is what I've figured out;
1) Use a singleton class for the locationManager, ONLY ONE Instance, follow the examples in:
Apple's LocateMe example
Tweetero : http://tweetero.googlecode.com/svn/trunk
I'm thinking you can only have one location manager running, there's only one GPS chip in the iPhone, thus if you launch multiple location manager object's they'll conflict, and you get errors from the GPS location manager stating it can't update.
2) Be extremely careful how you update the locationManager.desiredAccuracy
and
locationManager.distanceFilter
Follow the example in Apple's code for this in the FlipsideViewController:
[MyCLController sharedInstance].locationManager.desiredAccuracy = accuracyToSet;
[MyCLController sharedInstance].locationManager.distanceFilter = filterToSet;
This approach works, and causes no errors. If you update the desiredAccuracy or
distanceFilter in the main delegate loop:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
Core Location will likely complain that it can't update the values, and give you errors.
Also, only use the constants for the desiredAccuracy
supplied by Apple, and no others,
thus;
kCLLocationAccuracyBest, kCLLocationAccuracyNearestTenMeters,
kCLLocationAccuracyHundredMeters, kCLLocationAccuracyKilometer,
kCLLocationAccuracyThreeKilometers
Use of other values might give you errors from locationManager.
For distanceFilter
you can use any value, but be aware people have stated it has issues,
the main value to default to is: -1 = Best, I've used 10.0 and it seems OK.
3) To force a new update, call the startUpdates
method like in Tweetero, this forces
locationManager to do it's thing, and it should try and get you a new value.
4) The core location delegate routine is tricky to get accurate updates out of.
When your App first initializes you can be off in accuracy by 1000 meters or more, thus
one attempt is not going to do it. Three attempts after initialization usually gets you
to within 47 or so meters which is good. Remember that each successive attempt is going
to take longer and longer to improve your accuracy, thus it gets expensive quickly.
This is what you need to do:
Take a new value only if it is recent AND
If the new location has slightly better accuracy take the new location OR
If the new location has an accuracy < 50 meters take the new location OR
If the number of attempts has exceeded 3 or 5 AND the accuracy < 150 meters AND
the location has CHANGED, then this is a new location and the device moved so take this
value even if it's accuracy is worse.
Here's my Location code to do this:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
locationDenied = NO;
if(![[NSUserDefaults standardUserDefaults] boolForKey:@"UseLocations"])
{
[self stopAllUpdates];
return;
}
NSDate* eventDate = newLocation.timestamp;
NSTimeInterval howRecent = abs([eventDate timeIntervalSinceNow]);
attempts++;
if((newLocation.coordinate.latitude != oldLocation.coordinate.latitude) && (newLocation.coordinate.longitude != oldLocation.coordinate.longitude))
locationChanged = YES;
else
locationChanged = NO;
#ifdef __i386__
//Do this for the simulator since location always returns Cupertino
if (howRecent < 5.0)
#else
// Here's the theory of operation
// If the value is recent AND
// If the new location has slightly better accuracy take the new location OR
// If the new location has an accuracy < 50 meters take the new location OR
// If the attempts is maxed (3 -5) AND the accuracy < 150 AND the location has changed, then this must be a new location and the device moved
// so take this new value even though it's accuracy might be worse
if ((howRecent < 5.0) && ( (newLocation.horizontalAccuracy < (oldLocation.horizontalAccuracy - 10.0)) || (newLocation.horizontalAccuracy < 50.0)
|| ((attempts >= attempt) && (newLocation.horizontalAccuracy <= 150.0) && locationChanged)))
#endif
{
attempts = 0;
latitude = newLocation.coordinate.latitude;
longitude = newLocation.coordinate.longitude;
[currentLocation release];
currentLocation = [newLocation retain];
goodLocation = YES;
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateLocationNotification" object: nil];
if (oldLocation != nil) {
distanceMoved = [newLocation getDistanceFrom:oldLocation];
currentSpeed = newLocation.speed;
distanceTimeDelta = [newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp];
}
}
// Send the update to our delegate
[self.delegate newLocationUpdate:currentLocation];
}
If you have a iPhone 3GS getting Heading updates is a lot easier, here's the code in the same module as above:
//This is the Heading or Compass values
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
if (newHeading.headingAccuracy > 0)
{
[newHeading retain];
// headingType = YES for True North or NO for Magnetic North
if(headingType) {
currentHeading = newHeading.trueHeading;
} else {
currentHeading = newHeading.magneticHeading;
}
goodHeading = YES;
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateHeadingNotification" object: nil];
}
}
Hope this helps people!