MKMapView's user location is wrong on startup or resume
When I start my application fresh, or resume after a long time, MKMapView's notion of the userLocation is wrong and shows me in the middle of the sea.
I am using the following code:
self.mapView.centerCoordinate = self.mapView.userLocatio开发者_如何学运维n.location.coordinate;
[mapView setCenterCoordinate:self.mapView.userLocation.location.coordinate zoomLevel:ZOOM_LEVEL animated:YES];
Happens after a lengthy resume of the app or brand new start....
That's the expected behavior : the user location isn't always tracked by the iPhone using GPS (it would consume to much battery). So as soon as the map is displayed, the MKMapView
instance shows the last 'best' user position it knows and then, improves the accuracy by activating the tracking (this is a seamless process, you don't have to care about it) .
You can monitor when the MKMapView
updates the user location on the map by implementing the MKMapViewDelegate
protocol. Just implement :
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
CLLocationAccuracy accuracy = userLocation.location.horizontalAccuracy;
if (accuracy ......) {
}
}
(More info from the apple documentation here )
The code above in my example checks the accuracy of the position currently being displayed by the mapView and reacts accordingly.
[EDIT]
If showing the user location in the middle of the sea at first really bother you, you can you hide the user location until you get a location that is accurate/fresh enough.
To do so, set the showsUserLocation
property of the MKMapView
to NO
at first until you get an accurate enough location (thanks to the previous delegate callback) and then set it to YES
.
By doing you, you will avoid displaying a location that is not accurate or too old to be diplayed (there is a timestamp
property in the CLLocation
to check wether it's an old location or not)
N.B:You don't have to create a CLLocationManager
instance on your side, the MKMapView creates one internally and publish locations it receives via this delegate selector.
When using CLLocationManager directly, you normally get a cached location in the first callback. It's normally a good location, although old. After that you quickly get additional callbacks giving better locations using wifi, cell tower (if available). If you have asked for < 1000m accuracy you will (after more seconds) get GPS triangulation.
None of those should be inaccurate enough to be in the middle of the ocean. I suspect that the this line of code:
self.mapView.centerCoordinate = self.mapView.userLocation.location.coordinate;
is accessing the coordinate while userLocation
or location
is nil
. If userLocation
or location
is nil, this will return 0 coordinates. The location of lat=0, lon=0 is in the Atlantic Ocean, off the coast of Africa. You could add a check of location
to make sure it is not nil before getting the coordinate from it, ie:
if (self.mapView.userLocation.location) {
self.mapView.centerCoordinate = self.mapView.userLocation.location.coordinate;
[mapView setCenterCoordinate:self.mapView.userLocation.location.coordinate zoomLevel:ZOOM_LEVEL animated:YES];
}
You will also want to wait for callbacks to the MKMapViewDelegate mapView:didUpdateUserLocation:
to know when there is a valid location available. Your implementation of didUpdateUserLocation:
should discard any location that has a horizontalAccuracy < 0 which indicates an invalid location.
-(void)mapView:(MKMapView*)mapView didUpdateUserLocation:(MKUserLocation*)userLocation
{
if (userLocation.location.horizontalAccuracy > 0) {
[mapView setCenterCoordinate:self.mapView.userLocation.location.coordinate zoomLevel:ZOOM_LEVEL animated:YES];
}
}
This is unfortunately a function of the GPS chip. It's not always on, so the data will usually be wrong for the first couple of moments. Your best bet would probably be to store the last position recorded by the app in NSUserDefaults, then wait for the precision to be where you want it to be before switching to live data, or else hide the MkMapView until the precision is where you want it to be, then display it at that point (you could show a loading screen in the interim)
Just as Jeremy Massel wrote you have to sort out the first bad positions on app start / continue. Found a great blog post a couple of months ago:
http://troybrant.net/blog/2010/02/detecting-bad-corelocation-data/
What I do here is-
- start location updates
- on location update, check the age of location, if it's too old, I wait
- on receiving new update I update it on the map
If you need very accurate location, then put a check on accuracy as well and a timeout beyond which you can't tolerate waiting and use the less accurate fix.
How far off is it from your actual location? When an application just starts or resumes from a long period of time the user location is general always wrong to begin with. As the application runs it will get more and more accurate. There are methods for getting the accuracy of the user location in CLLocationManager, see what kind of values you get there.
In my apps I used these methods:
First - it's good to have a default position (for example - application is about a city - then zoom in to show whole city) (on first - first app opening, when no data downloaded)
Second - if user has already used map - can store his coordinates, and show those (+ span(zoom) level) in case there are no other data available.
Third - in case of data:
1.) If database has already some coordinates - on first map opening (when application has been closed) - zoom out/in to show all pins on map.
2.) When still in map and just arrived updates (new locations or changed something), there are two possibilities :
2.1.) If user has not zoomed in to any location - map zooms in/out to show all locations.
2.2.) if user has zoomed in - locations are updated, but map doesnt zoom out
Ofcourse - then there are also some filter buttons (for example : search, user location, and some more - which filters locations, and zooms out/in to show filtered results)
精彩评论