Unrecognized Error in MapView (iOS) - ios

I'm getting an error in MapView that I don't recognize and can't find documentation on. It looks like this:
CoreAnimation: ignoring exception:
Invalid Region <center:-180.00000000, -180.00000000
span:+2.81462803, +28.12500000>
Obviously the numbers are exclusive to my code right now but I can't figure out what's going on. The MapView runs just fine and all of my annotations show up (and it zooms on the user's location like I have it set). What specifically does this point to?
Thanks.
Here's the method I use to zoom to the user's location. It's a little unorthodox but it's what I've been helped with since I had problems with zooms for a variety of reasons (I can explain if need be, but it's probably not relevant):
- (void)zoomToUserLocation:(MKUserLocation *)userlocation
{
if (!userlocation)
return;
MKCoordinateRegion region;
region.center = userlocation.coordinate;
region.span = MKCoordinateSpanMake(2.0, 2.0);
region = [self.mapView regionThatFits:region];
[self.mapView setRegion:region animated:YES];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self zoomToUserLocation:self.mapView.userLocation];
}
- (void)mapView:(MKMapView *)theMapView didUpdateUserLocation:(MKUserLocation *)location
{
[self zoomToUserLocation:location];
}

Can't tell where the invalid coordinates are coming from but I suggest adding the following checks to the zoomToUserLocation method.
Just checking if userlocation is nil is not enough. You have to also check if the location property inside userlocation is nil or not. Then, you can use the coordinate property (especially when you're using the coordinates outside the didUpdateUserLocation delegate method).
Also, just checking if coordinate is 0,0 (technically a valid coordinate) is not recommended as the struct will be "zero" if it's never been set or it could even be filled with random data. The Core Location framework's CLLocationCoordinate2DIsValid function is used as the last line of defense to prevent an invalid region.
You could also check the timestamp and horizontalAccuracy if you want.
- (void)zoomToUserLocation:(MKUserLocation *)userlocation
{
if (!userlocation)
return;
if (!userlocation.location)
{
NSLog(#"actual location has not been obtained yet");
return;
}
//optional: check age and/or horizontalAccuracy
//(technically should check if location.timestamp is nil first)
NSTimeInterval locationAgeInSeconds =
[[NSDate date] timeIntervalSinceDate:userlocation.location.timestamp];
if (locationAgeInSeconds > 300) //adjust max age as needed
{
NSLog(#"location data is too old");
return;
}
if (!CLLocationCoordinate2DIsValid(userlocation.coordinate))
{
NSLog(#"userlocation coordinate is invalid");
return;
}
MKCoordinateRegion region;
region.center = userlocation.coordinate;
region.span = MKCoordinateSpanMake(2.0, 2.0);
//region = [self.mapView regionThatFits:region];
//don't need to call regionThatFits explicitly, setRegion will do it
[self.mapView setRegion:region animated:YES];
}
Additionally (possibly unrelated and you may have already done this but), based on a couple of your previous questions related to this, you might want to clear and re-set the map view's delegate in the map view controller's viewWillDisappear and viewWillAppear methods to prevent certain errors:
-(void)viewWillAppear:(BOOL)animated
{
mapView.delegate = self;
}
-(void)viewWillDisappear:(BOOL)animated
{
mapView.delegate = nil;
}

I have found that if you have location services enabled and then display a map view that contains the current user location as an annotation, then disable location services and attempt to use the "location" property of the annotation, the result will be (-180, -180).

Related

MKMapView does not move and zoom to search results

I'm implementing search in my MKMapView and I've faced two problems:
When I perform search, location appears in the result and map moves to the found location only after I start to move to the destination. This happens, when the search results are out of the view bounds. If they are inside of the map view bounds or near them it's fine.
It "hops" all the time from one search result to another or to the user's location. I don't expect such behaviour from it.
I've tried several things and I suppose, that the problem is in: didAddAnnotationViews:
[self.locationManager stopUpdatingLocation];
MKAnnotationView *annotationView = [views objectAtIndex:0];
NSLog(#"_Here_ %#", [views description]);
id<MKAnnotation> mp = [annotationView annotation];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([mp coordinate], 250, 250);
[mv setRegion:region animated:YES];
[self.mapView selectAnnotation:mp animated:YES];
Though, I also thought that the problem is in didUpdateToLocation, so I disable updating after the first pin is drop (by search or by tap):
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
double miles = 0.3;
double scalingFactor =
ABS( cos(2 * M_PI * newLocation.coordinate.latitude /360.0) );
MKCoordinateSpan span;
span.latitudeDelta = miles/69.0;
span.longitudeDelta = miles/( scalingFactor*69.0 );
MKCoordinateRegion region;
region.span = span;
region.center = newLocation.coordinate;
[self.mapView setRegion:region animated:YES];
self.mapView.showsUserLocation = YES;
}
Finally, search method:
-(void)searchBarSearchButtonClicked:(UISearchBar *)theSearchBar
{
MKLocalSearchRequest *searchRequest = [[MKLocalSearchRequest alloc] init];
[searchRequest setNaturalLanguageQuery:theSearchBar.text];
searchRequest.region = MKCoordinateRegionMakeWithDistance(self.mapView.userLocation.coordinate, 1000, 1000);
MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:searchRequest];
[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
if (!error) {
NSMutableArray *annotations = [NSMutableArray array];
[response.mapItems enumerateObjectsUsingBlock:^(MKMapItem *item, NSUInteger idx, BOOL *stop) {
for (id<MKAnnotation>annotation in self.mapView.annotations)
{
if (annotation.coordinate.latitude == item.placemark.coordinate.latitude &&
annotation.coordinate.longitude == item.placemark.coordinate.longitude)
{
return;
}
}
MKPointAnnotation *addAnnotation = [[MKPointAnnotation alloc] init];
addAnnotation.title = [item.placemark.addressDictionary objectForKey:#"Street"];
addAnnotation.coordinate = item.placemark.coordinate;
[annotations addObject:addAnnotation];
}];
for (id<MKAnnotation>annotation in self.mapView.annotations) {
[self.mapView removeAnnotation:annotation];
}
[self.mapView addAnnotations:annotations];
} else {
NSLog(#"Search Request Error: %#", [error localizedDescription]);
}
}];
//Hide the keyboard.
[self.searchBar resignFirstResponder];
}
My aim is to create a MapView, where user can pin the location by tap or via search and, obviously, see the search result.
For the first problem:
When I perform search, location appears in the result and map moves to
the found location only after I start to move to the destination. This
happens, when the search results are out of the view bounds. If they
are inside of the map view bounds or near them it's fine.
This happens because you are moving the map to the annotations found (at least the first one) in the didAddAnnotationViews delegate method.
But that delegate method is only called when an annotation is in the visible area. If an annotation is added to the map but it's not in the visible area (yet), viewForAnnotation won't get called and therefore didAddAnnotationViews won't get called.
Then, when you manually move the map, the annotations that were added start coming into the visible area and then the delegate method gets called and suddenly the map jumps to one of those annotations.
Don't call setRegion inside the didAddAnnotationViews delegate method.
Sometimes, doing so can also cause an endless cycle of viewForAnnotation and didAddAnnotationViews calls because when the region is changed, it causes other annotations to come into view that weren't previously, so viewForAnnotation gets called and then didAddAnnotationViews gets called, and so on.
Instead, set the region right after you call addAnnotations: (or better, just call showAnnotations:) in the searchBarSearchButtonClicked: method.
I would also remove the call to stopUpdatingLocation from didAddAnnotationViews. You probably don't even need the location manager at all if you set the map's showsUserLocation to YES.
For the second problem:
It "hops" all the time from one search result to another or to the
user's location. I don't expect such behaviour from it.
This is also partly due to calling setRegion in didAddAnnotationViews but also because setRegion is called in didUpdateToLocation.
So for the reason described for the first problem, the two delegate methods and the user's manual movements are fighting with each other and the map ends up hopping around.
Don't call setRegion in the didUpdateToLocation method (or, call it once by keeping track in a BOOL whether you've already zoomed to the user location or not).
Not affecting the behavior, but setting showsUserLocation to YES in the didUpdateToLocation doesn't make sense. Why not set this in viewDidLoad or turn it on in the storyboard/xib?
Also, there's no need to calculate the region span manually like that (it's better to let the MapKit do that work for you). Just convert the miles to meters and call MKCoordinateRegionMakeWithDistance.

How to fix MapView zoom into location until a second time view appears

I have an app that loads a tabbarcontroller with 3 tabs. One of them is a mapview. It is set to zoom into the user's location by this code:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
_mapView.showsUserLocation = YES;
CLLocationCoordinate2D zoomLocation;
//IF no city was selected, use userLocation as center
if (!self.cityWasSelected) {
zoomLocation.latitude = self.userLocation.coordinate.latitude;
zoomLocation.longitude = self.userLocation.coordinate.longitude;
CLLocationDistance visibleDistance = 5000; // 5 kilometers
MKCoordinateRegion adjustedRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, visibleDistance, visibleDistance);
[_mapView setRegion:adjustedRegion animated:YES];
} else { //if a city was selected, use that city's value...this is actually the same right now, since self.userLocation is set appropriately elsewhere.
//Set location from selection - forward geocode
zoomLocation.latitude = self.userLocation.coordinate.latitude;
zoomLocation.longitude = self.userLocation.coordinate.longitude;
CLLocationDistance visibleDistance = 5000; // 5 kilometers
MKCoordinateRegion adjustedRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, visibleDistance, visibleDistance);
[_mapView setRegion:adjustedRegion animated:YES];
}
}
The initial tab is a tableviewcontroller for user preferences, the second tab is the mapview and the third is a tableview. When I first tap on the mapview, the map shows the entire world :) If i tap back to the initial tab or the list tab and then return to the mapview, the map is properly centered around my current location.
Why does this happen?
Setting showsUserLocation on the map view will start searching for the user's location. This is an asynchronous operation and you cannot assume that mapView.userLocation will be valid immediately after settings showsUserLocation to YES.
The first time you view appears, you ask the map view to start location. mapView.userLocation probably returns nil. The second time around, the map view has probably finally gotten the user location and loads your map region successfully. This is sheer luck, and you should not rely that you will get location the second time (it may fail, take longer than usual).
When the map view has determined user location, it will call back on a delegate method (MKMapViewDelegate). You need to implement this delegate method like so:
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
// We have location, do your logic of setting the map region here.
...
}
Hope this helps.

Drop a pin on MKMapView

iPhone newbie is here coming from Java. So my objective at this stage is to allow the user to 'drop a pin' on the map. My initialization of the map looks like this:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"your view did load, I'm going to initizlie the map by your location");
CLLocationCoordinate2D location = theMap.userLocation.coordinate;
NSLog(#"Location found from Map: %f %f",location.latitude,location.longitude);
MKCoordinateRegion region;
MKCoordinateSpan span;
NSLog(#"coordinates: %f %f",location.latitude,location.longitude);
if (TARGET_IPHONE_SIMULATOR) {
NSLog(#"You're using the simulator:");
location.latitude = 40.8761620;
location.longitude = -73.782596;
} else {
location.latitude = theMap.userLocation.location.coordinate.latitude;
location.longitude = theMap.userLocation.location.coordinate.longitude;
}
span.latitudeDelta = 0.001;
span.longitudeDelta = 0.002;
region.span = span;
region.center = location;
[theMap setRegion:region animated:YES];
[theMap regionThatFits:region];
[theMap setMapType:MKMapTypeSatellite];
[theMap setZoomEnabled:YES];
[theMap setScrollEnabled:YES];
[theMap setShowsUserLocation:YES];
}
For the requested pin drop I have
- (MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = nil;
if (annotation != theMap.userLocation) {
static NSString *defaultPinID = #"aPin";
pinView = (MKPinAnnotationView *)[theMap dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if (pinView == nil)
pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID] autorelease];
} else {
}
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
pinView.animatesDrop = YES;
return pinView;
}
I'm not sure I fully understand how this map (theMap) works for pins in viewForAnnotation? I mean, what action the user does will activate the viewForAnnotation method? This code doesn't work and I'm not sure why.
I'm using the simulator so I'm not sure if there's a button I should press or Alt click it?
I'm not sure I fully understand how this map (theMap) works for pins in viewForAnnotation?
MKPinAnnotationView is just another kind of annotation view -- that is, you add an annotation (an object conforming to the MKAnnotation protocol) to the map. When the map wants to display the annotation (maybe because the user scrolled the map so that the annotation is in view), it asks you for a view to use to represent the annotation. At that point, your mapView:viewForAnnotation: method can fetch or create a pin annotation view and return that. The user doesn't do anything directly to trigger mapView:viewForAnnotation:, except for scrolling or zooming.
If you want to the user to be able to drop a pin, that's a different thing. You'll need to provide a view (possibly even a MKPinAnnotationView) that they can drag around. When they indicate that they want to drop the pin (perhaps by lifting their finger), you remove the view and add an appropriate annotation at that point. Then the map view will ask you for a view to represent the annotation by calling its delegate's mapView:viewForAnnotation: method.
This code doesn't work and I'm not sure why.
Have you added any annotations to the map? If so, are you looking at the part of the map where they should be displayed?
I'm guessing that you're looking at the animatesDrop property and expecting it to do the entire user pin-dropping interaction. It doesn't do that. Setting that property to YES merely animates the pin as it appears on the map.
ok, after a while, I understood what went wrong:
theMap.delegate = (id) self;
in the constructor was missing. Once I did that any action by end user will activate other methods (protocols) of the map.

iOS MKMapView starts zoomed out default map but zooms in only on refresh

Everytime I start the app, the first time seeing the map results in a default map that is always zoomed out with no annotations. When I go back on the navigation controller and go back into the map, it now shows the correct region with the appropriate pins. The code I use to add the
- (void) zoomIn {
mapView.showsUserLocation = YES;
CLLocationCoordinate2D annotation;
annotation.latitude = 47.640071;
annotation.longitude = -122.129598;
MKPointAnnotation *annoPoint = [[MKPointAnnotation alloc] init];
annoPoint.coordinate = annotation;
annoPoint.title = #"name";
[mapView addAnnotation:annoPoint];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(annotation, 500, 500);
[mapView setRegion:region animated:YES];
}
I call this block of code from the viewDidLoad, but it only works after I go back to the main page from the navigation controller and enter this UIViewController again.
Does anyone know what the problem is or have seen it before?
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
// this delegate fonction is called when the userlocation is updated
// try to move your code here
}
you have also
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
}
hope this helps
Show us your viewDidLoad function, you're probably calling zoomIn too early, maybe before your MKMapView has been initialized.

How do I center over a specific location and then zoom to current location

Very much a newbie here so please forgive the ignorance. I have spent some time trying to understand what I am missing but cannot figure it out.
My app centers over Washington State when loading but when I try to zoom to the users current location it puts me at latitude 0 longitude 0. If I comment out the "// startup: center over WA" section it centers over the users current location and then goToLocation works fine.
How do I get it to center over Washington State and then zoom to the users current location upon clicking goToLocation?
Thanks!
- (void)viewDidLoad {
[super viewDidLoad];
[self loadOurAnnotations];
[mapView setShowsUserLocation:NO];
// startup: center over WA
CLLocationCoordinate2D defaultCoordinate;
defaultCoordinate.latitude = 47.517201;
defaultCoordinate.longitude = -120.366211;
[mapView setRegion:MKCoordinateRegionMake(defaultCoordinate, MKCoordinateSpanMake(6.8, 6.8)) animated:NO];
}
-(IBAction)goToLocation {
MKUserLocation *myLocation = [mapView userLocation];
CLLocationCoordinate2D coord = [[myLocation location] coordinate];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(coord, 350, 350);
[mapView setRegion:region animated:YES];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[UIView commitAnimations];
}
First, to use userLocation in the MKMapView at all, you have to pass YES to setShowsUserLocation (not NO).
Next thing is that after turning showsUserLocation on, it may take a few seconds or more for the map view to determine the location and set userLocation. Until then, the location will be nil (giving coordinates of 0,0).
To really know when the userLocation is ready (or updated), implement the didUpdateUserLocation delegate method. It's also helpful to implement the didFailToLocateUserWithError method in case of a problem determining the user location.
However, in your case, you could just do the following in the goToLocation method:
MKUserLocation *myLocation = [mapView userLocation];
if (myLocation.location == nil)
{
NSLog(#"user location has not been determined yet");
return;
}
CLLocationCoordinate2D coord = [[myLocation location] coordinate];
MKCoordinateRegion region = ... //rest of the code stays the same
The animation statements at the end of that method don't do anything, by the way.

Resources