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.
An old question that I would like to answer my own way. I was having an issue somewhat similar. I just needed to make a web service call, passing the user location as a GET parameter, when my MapView's ViewController/Screen was loaded and user location retrieved by the mapView. It made sense then to call the web service within the delegate method didUpdateUserLocation. I didn't notice the wrong behaviour first because things seemed to work properly but then for some reasons sometimes upon opening the mapView screen, the user blue dot would show but the map would not "zoom in" and neither didUpdateUserLocation was called nor my inner web service call obviously. After a few seconds of staring at the screen, the map would "zoom in" and the didUpdateUserLocation/web service was called. Some small glitch I thought, not a big deal. Now my detail-oriented developer's mind was still frustrated and after a few weeks of thinking this over, I decided to take action on this. Stackoverflow didn't give me the answer straight away but pointed me towards the right direction nonetheless. And here was the culprit: Sequence of calls! Maddening but made total sense once I figured things out. I knew that in order to see the blue dot and get the user location I had to tell the mapView to do so. So being an Interface Builder lover, I set things up properly for my mapView in IB, that is I checked the box: "User Location", easy. Then carefully reading the mapView documentation I realised that my ViewController needed to conform to MKMapViewDelegate, done deal. As I said, things seemed to work ok, the blue dot would show right away but sometimes the "zoom in" of the map would take a few seconds... well my iPhone is already 3 years old, things are getting slow, I'd deal with the sluggishness... Then I read this stack overflow post (https://stackoverflow.com/a/37407955) and things became clear. In my case, since using IB, here was the sequence of calls:
mapView.delegate = self
Whereas, here is how the sequence of calls should have been:
mapView.delegate = self
mapView.showsUserLocation = true
And this changes EVERYTHING. Instead of having the mapView zooming in sometimes right away and sometimes after a few seconds, the mapView now zooms in right away when the screen opens, and the didUpdateUserLocation/web service IS called.
Now a little more on the why, it still "sometimes" worked. This is simply due to the fact that the location of my iPhone was sometimes updated right after the map screen was loaded and sometimes not :-). I have to say that besides the stackoverflow post I mentioned above, what helped me too was testing my app in the Simulator since I needed to use a gpx file with obviously static location coordinate, the non predictable behaviour I described was then systematic.
So as much as I love Interface Builder, I might have to reconsider how unconditional that love is.
PS: I know this post is old and my answer not entirely related to the issue @Patrick Perini had but I wish it helps others and that it might answer @Patrick Perini's conclusion regarding the fact that a Bug was at fault when it's not. Thanks for reading this blog post :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With