I have an MKMapView
that's supposed to track the user's location using a custom view (not the blue dot). In order to substitute this view for the blue dot, I return it thusly:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if (annotation == [mapView userLocation])
{
return userLocationView;
}
}
To initialize tracking, I call
[mapView setShowsUserLocation: YES];
[mapView setUserTrackingMode: MKUserTrackingModeFollow animated: NO];
[mapView setDelegate: self];
As would be expected, -mapView:didUpdateUserLocation:
gets called once when the app loads. Unfortunately, it's never called again unless I change -mapView:viewForAnnotation:
to have the following implementation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if (annotation == [mapView userLocation])
{
return nil;
}
}
With these changes, the map loads the blue dot as the indicator of the user's location, and -mapView:didUpdateUserLocation:
gets called frequently, as would be expected.
Is there some sort of mutual exclusivity for tracking users' locations and have a custom user location view? How can I make both happen?
This project demonstrates this issue. https://dl.dropbox.com/u/2338382/MapKitFuckery.zip
This is most likely a bug, which I've filed as a radar. In the interim, the accepted answer should prove sufficient. However, it bears noting that I had to give up entirely on [mapView userLocation]
and [mapView showsUserLocation]
, in favor of simply a custom annotation and the CLLocationManager
.
Instead of relying on the map view's location updates, start a CLLocationManager
, set its delegate
and wait for -locationManager:didUpdateToLocation:fromLocation:
(in iOS 5 and lower) or -locationManager:didUpdateLocations:
(iOS 6). You will get much more reliable and plentiful information than using the map view's delegate methods. You probably know the way to do this, but here it is:
#import <CoreLocation/CoreLocation.h>
- (void)viewWillAppear:(BOOL)animated
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[self.locationManager startUpdatingLocation];
}
// Deprecated in iOS 6
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
// Check the age of the newLocation isn't too long ago using newLocation.timestamp
// Set the map dot using newLocation.coordinate
// Set an MKCircle to represent accuracy using newLocation.horizontalAccuracy
}
I had a look at the delegate calls that come in to the mapView's delegate, and returning anything other than nil
stops calls to -mapView:didUpdateUserLocation:
, like you said. Here are the calls in the order they arrive:
- (void)mapViewWillStartLocatingUser:(MKMapView *)mapView
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation
- (void)mapViewWillStartLoadingMap:(MKMapView *)mapView
- (void)mapView:(MKMapView *)mapView didFailToLocateUserWithError:(NSError *)error
Presumably the MKUserLocation
object, not the MKMapView
is the object responsible for calling the delegate with update calls. If you check the status of showsUserLocation
and mapView.userLocation
, they both look fine:
NSLog(@"%d %@", mapView.showsUserLocation, mapView.userLocation);
returns 1
and a non-nil object (1 <MKUserLocation: 0x1e02e580>
). Maybe the mapView
queries its userLocation
object to get the current location, then sends it to the delegate. If that object has gone, it won't work.
It's a bit strange, but like I said, you'll get better updates from a CLLocationManager
's updates.