I've created a test application with only one view containing an MKMapView and a controller which acts as the MapView's delegate.
When I do a fresh build (removed from the device completely before re-installing) and log the callbacks, I can see that mapView:didUpdateUserLocation is called twice before the user has indicated whether they wish to show their current location or not.
The MKUserLocation objects passed to the callback are invalid:
2012-03-13 08:20:17.518 MapTest[3325:707] Did update user location: 0.000000,0.000000
2012-03-13 08:20:17.581 MapTest[3325:707] Did update user location: 0.000000,0.000000
Is this the expected behaviour for MKMapKit or a bug?
Update
I'm running this on my iPhone 4, not a simulator.
Here's the controller code:
#import "ViewController.h"
#implementation ViewController
#synthesize mapView;
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.showsUserLocation = YES;
self.mapView.delegate = self;
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
-(IBAction)trackButtonTapped:(id)sender
{
self.mapView.showsUserLocation = !self.mapView.showsUserLocation;
}
#pragma mark - MKMapKitDelegate
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
NSLog(#"Did update user location: %f,%f", userLocation.coordinate.latitude, userLocation.coordinate.longitude);
}
-(void)mapViewWillStartLoadingMap:(MKMapView *)mapView
{
NSLog(#"Will start loading map");
}
-(void)mapViewDidFinishLoadingMap:(MKMapView *)mapView
{
NSLog(#"Did finish loading map");
}
-(void)mapViewWillStartLocatingUser:(MKMapView *)mapView
{
NSLog(#"Will start locating user");
}
-(void)mapViewDidStopLocatingUser:(MKMapView *)mapView
{
NSLog(#"Did stop locating user");
}
-(void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error
{
NSLog(#"Did fail loading map");
}
-(void)mapView:(MKMapView *)mapView didFailToLocateUserWithError:(NSError *)error
{
if (error.code == kCLErrorDenied){
NSLog(#"User refused location services");
} else {
NSLog(#"Did fail to locate user with error: %#", error.description);
}
}
#end
My opinion is that it's a bug.
How can the map view say user location has been updated when the user hasn't even granted permission?
The workaround I use is to check if userLocation.location is nil:
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
if (userLocation.location == nil)
return;
//do something with userLocation...
}
instead checking for nil userlocation (where 0,0 coordinates can actually be valid) you should use:
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
// do something with the coords
} else {
// the coords are invalid
}
With "self.mapView.showsUserLocation = YES;" in viewDidLoad it will start trying to get the user's location straight away. If you take that out it'll wait until the user has pressed a button.
Maybe this helps someone.
For me the "problem" was that I had set show user's location trough interface builder too. Removing it here and setting it through code made the delegate methods get work as suspected.
Related
I am building an iOS application using google maps SDK. I can add some markers on the maps when user does a longPressAtCoordinate. My problem is that when I am trying to drag a marker the diiLongPressAtCoordinate is fired before the didBeginDraggingMarker so a new marker is added also.
-(void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker{
NSLog(#"begin dragging marker");
}
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate (CLLocationCoordinate2D)coordinate{
NSLog(#"did long press at mapview");
//when user didLongPressAtCoordinate I add a new marker on the map.
// I want to prevent the execution of this code before the didBeginDraggingMarker method
}
I solved this problem by creating a boolean property called isDragging and changing it's value depending whether a marker is being dragged.
- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker
{
self.isDragging = YES;
}
- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker
{
self.isDragging = NO;
}
Then I validate if a marker is being dragged whenever a long press is detected:
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate
{
if (self.isDragging) {
return;
}
NSLog(#"Long press detected");
}
I am adding some functionality in
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
and
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
I want this to be called when the map region is changed.
How can i prevent these delegate methods from being called when device changes its orientation?
Add a property BOOL didRotate
Then add something like
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
self.didRotate = YES;
}
and
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated{
if(self.didRotate) return;
}
and
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
if(self.didRotate) {
self.didRotate = NO;
return;
}
}
These delegate methods are called when the frame of the map view changes. The frame can change on rotation because of autolayout constraints or autoresizing masks. So one solution would be to keep the map view frame constant.
You could also try to unset the delegate while the interface orientation is changing.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
_mapView.delegate = nil;
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
_mapView.delegate = self;
}
I have a mapViewController (in a Navigation Controller). When I open it for the first time, after viewDidLoad, - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation gets called.
When I go back to the previous viewController and come again to the mapViewController, the didUpdateUserLocation delegate is not being called, hence my annotations are not getting shown.
Please help me in finding the solution to the problem. Thank you.
If you want the map view to continue to track the user’s location and update it periodically, you should set MapView showsUserLocation to YES (the default value is NO).
Try
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
...
[mapView setShowsUserLocation: YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
...
[mapView setShowsUserLocation: NO];
}
Apple Doc: http://developer.apple.com/library/ios/#DOCUMENTATION/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html
Call this method in - view will appear method of same class
[locationManager startUpdatingLocation];
From a detail view of an event I want to go to a MapViewController, zoom in to the annotation, and open it's callout.
Here is some of the relevant code:
#interface MapViewController : UIViewController<MKMapViewDelegate>
...
- (void) viewWillAppear:(BOOL)animated
{
[self displayAnnotations];
}
- (void) viewDidAppear:(BOOL)animated
{
...
// Zoom in to event
[map setRegion:region animated:YES];
}
- (void) mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
...
regionAnimationEnded = YES;
[self selectAnnotation:a];
...
}
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
...
// check to see if the right view is in the array
...
annotationViewDidAppear = YES;
[self selectAnnotation:a];
...
}
- (void) selectAnnotation:(id<MKAnnotation>)annotation
{
if(annotationViewDidAppear && regionAnimationEnded)
{
if(!openedAnnotationFirstTime)
{
[map selectAnnotation:annotation animated:YES];
openedAnnotationFirstTime = YES;
}
}
}
This works on the ios 6 simulator, but on the ios 5.1 simulator (and on the device) the annotion view isn't visible as it says in the docs:
(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
By the time this method is called, the specified views are already added to the map.
So it depends which finishes first: if the region change animation finishes last and the annotation view has appeared it works, otherwise it doesn't.
Any help would be appreciated.
Your selectAnnotation: method is being called twice. Try calling it only once, in mapView:regionDidChangeAnimated: delegate method.
I ended up using perform selector with delay on the actual [map selectAnnotation:annotation animated:YES];
It's a work-around but seems to work nicely.
I need some help with an app i'm making using MapKit
I'm struggling with the didUpdateUserLocation: - it keeps randomly crashing the app. When I comment out all of the code it works perfectly but this isn't a suitable situation. I've been fiddling around with it all but still no real success.
Here's my code
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
NSLog(#"update loc");
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 250.0, 250.0);//MKCoordinateRegionMake(userLocation.coordinate, mapView.region.span);
if (first) {
region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 250.0, 250.0);
first = false;
}
if (!CLLocationCoordinate2DIsValid(userLocation.coordinate)) {
//do nothing, invalid regions
NSLog(#"co-ord fail");
} else if (region.span.latitudeDelta <= 0.0 || region.span.longitudeDelta <= 0.0) {
NSLog(#"invalid reg");
} else {
[mapView setRegion:region animated:NO];
}
}
The app never hits "co-ord fail" or "invalid reg" so I don't really know what the problem is since I set the values myself.
The problem usually occurs when the nib for the Map is closed and it has dealloced the view.
I have seen some other suggestions but haven't been confident about their work. Ideally I'd like to use mapView.region.span so to maintain the zoom levels
Many Thanks,
James
Watch out that if an App that was suspended a short while, on resume the
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
delegate receives as the first userLocation (0.0,0.0) as latitude,longitude! I think that is a bug.. Check:
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
NSLog( #"userLocation:(%p) %#",userLocation,userLocation);
NSLog( #"userLocation.location:(%p) %#",userLocation.location,userLocation.location);
NSLog( #"userLocation.coordinate: %f,%f valid:%d",userLocation.coordinate.latitude,userLocation.coordinate.longitude, CLLocationCoordinate2DIsValid(userLocation.coordinate));
NSLog( #"userLocation.location.coordinate: %f,%f valid: %d",userLocation.location.coordinate.latitude,userLocation.location.coordinate.longitude, CLLocationCoordinate2DIsValid(userLocation.location.coordinate));
Result:
userLocation:(0x193fc0) <MKUserLocation: 0x193fc0>
userLocation.location:(0x0) (null)
userLocation.coordinate: -180.000000,-180.000000 valid:0
userLocation.location.coordinate: 0.000000,0.000000 valid: 1
Odd that 0,0 is produced from userLocation.location.coordinate while userLocation.location is nil, resulting in a valid coordinate, even!
Indeed the check on userLocation.location being nil seems best.
The problem usually occurs when the nib for the Map is closed and it
has dealloced the view.
[mapView setRegion:region animated:NO];
Well isn't that your problem then?
This is why we have dependency inversion patterns like Observer/Listener.
Instead of fiddling around with the view directly with your CLLocationManager delegate, use NSNotification to alert subscribing views (such as your MapView) that it should update. If the MapView has been dealloced it won't do anything.