Detecting when a user scrolls MKMapView a certain distance? - iphone

I want to determine if a user has scrolled more than a certain percentage of the map then disable centering of the map from the user location (similar to how the Maps app works).
I'm not sure which methods to make use of.
I think it would be straightforward to create a rectangle and see if the rectangle contains the current center point, however I have to target IOS 3, so I can't make use of many of the newer Mapkit apis.
I've tried futzing with CLLocation, and using distanceFrom, between the current mapcenter, and the users location, but I'm trying to figure out if that distance is a certain percentage.

I personally find it more helpful when someone can post a snippet of code versus general prose about how one might go about this. Here's what I came up with- roughly hacked out to simply better answer this question:
In a header file I have:
#define SCROLL_UPDATE_DISTANCE 80.00
and in my view (that is both a delegate for CLLocationManagerDelegate, MKMapViewDelegate):
// this method is called when the map region changes as a delegate of MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
NSLog(#"regionDidChangeAnimated");
MKCoordinateRegion mapRegion;
// set the center of the map region to the now updated map view center
mapRegion.center = mapView.centerCoordinate;
mapRegion.span.latitudeDelta = 0.3; // you likely don't need these... just kinda hacked this out
mapRegion.span.longitudeDelta = 0.3;
// get the lat & lng of the map region
double lat = mapRegion.center.latitude;
double lng = mapRegion.center.longitude;
// note: I have a variable I have saved called lastLocationCoordinate. It is of type
// CLLocationCoordinate2D and I initially set it in the didUpdateUserLocation
// delegate method. I also update it again when this function is called
// so I always have the last mapRegion center point to compare the present one with
CLLocation *before = [[CLLocation alloc] initWithLatitude:lastLocationCoordinate.latitude longitude:lastLocationCoordinate.longitude];
CLLocation *now = [[CLLocation alloc] initWithLatitude:lat longitude:lng];
CLLocationDistance distance = ([before distanceFromLocation:now]) * 0.000621371192;
[before release];
[now release];
NSLog(#"Scrolled distance: %#", [NSString stringWithFormat:#"%.02f", distance]);
if( distance > SCROLL_UPDATE_DISTANCE )
{
// do something awesome
}
// resave the last location center for the next map move event
lastLocationCoordinate.latitude = mapRegion.center.latitude;
lastLocationCoordinate.longitude = mapRegion.center.longitude;
}
Hope that sends you in the right direction.
distanceFromLocation is iOS 3.2 and later.
initWithLatitude is iOS 2.0 and later.
MKCoordinateRegion is iOS 3.0 and later.
MKMapView centerCoordinate is iOS 3.0 and later.
Also- please feel free to jump in and set me straight where I've erred. I'm figuring all of this out myself- but this is working fairly well for me so far.
Hope this helps someone.

First lesson: Don't ask questions late night on SO.
Second lesson: you can achieve this simply by construction a CGPoint from the user's current location, and a CGPoint from the MapView center.
With two points, just calculate the distance, and see if it's past a certain threshold.
You can also construct a CGRect around the map center, and check CGRectContainsPoint if that's easier.
- (BOOL) isUserPointInsideMapCenterRegion
{
CLLocation * ul = _mapView.userLocation.location;
CGPoint userPoint = [_mapView convertCoordinate: ul.coordinate toPointToView: _mapView];
CGPoint mapPoint = [_mapView convertCoordinate: _mapView.centerCoordinate toPointToView: _mapView];
if (fabs(userPoint.x - mapPoint.x) > MAP_CENTER_RECTANGLE_SIZE || fabs(userPoint.y - mapPoint.y) > MAP_CENTER_RECTANGLE_SIZE)
{
return NO;
}
return YES;
}

I realise this question is a bit old now, but I feel that the answer described in this other question is more robust because the delegate method could be fired for any reason. Using a UIPanGestureRecognizer to detect the scroll means that the user manually scrolled the map, and it can check if the map has scrolled X pixels, instead of relying on meters, which means the user has scrolled more or less depending on the zoom level.
https://stackoverflow.com/a/11675587/159758

Related

How to add distance to pin of an MKAnnotation?

Ive got an app that plots mkannotations (i hope i get my terminology right...its kinda confusing) on a mapview.
I have already included the subtitle for when you tap on them.
I have been looking online for a way to include the distance in those callouts but im not quite there yet. I ran across two partial solutions and Im wondering if they should be combined.
First, I didnt have CoreLocation added to my project, I need it right? To be constantly updating my user location and be able to calculate the distances to each point? Or does Mapkit somehow include a user location data that I can use?
Partial Solution A uses this code:
`-(void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
if(!newLocation) return;
if ((oldLocation.coordinate.latitude != newLocation.coordinate.latitude) &&
(oldLocation.coordinate.longitude != newLocation.coordinate.longitude)){
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:oldLocation.coordinate.latitude longitude:oldLocation.coordinate.longitude];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:newLocation.coordinate.latitude longitude:newLocation.coordinate.longitude];
CLLocationDistance distance = ([loc2 distanceFromLocation:loc1]) * 0.000621371192;
//distance = distance;
NSLog(#"Total Distance %f in miles",distance);
}
}
I understand this method calculates the distance between 2 points. I would somehow need to cycle thru my annotations and create the distance. It seems this would be the more useful one since it constantly recalculates the distances based on the current userLocation. Although, I do wonder about the effectiveness of that. Once you know how far away something is, you rarely wish to be constantly reminded as to how far away it is.
Partial Solution B uses this code:
`- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
CLLocation *pinLocation = [[[CLLocation alloc] initWithLatitude:[(MyAnnotation*)[view annotation] coordinate].latitude longitude:[(MyAnnotation*)[view annotation] coordinate].longitude]];
CLLocation *userLocation = [[CLLocation alloc] initWithLatitude:self._mapView.userLocation.coordinate.latitude longitude:self._mapView.userLocation.coordinate.longitude];
CLLocationDistance distance = [pinLocation distanceFromLocation:userLocation];
NSLog(#"Distance to pin %4.0f", distance);
}
`
In this case, whenever the pin is tapped, the distance is calculated. But Im unclear as to the code for MyAnnotation [view annotation], Im guessing the original poster had his locations based off of a MyAnnotation Class so I changed it to MyLocation and all but 1 error went away. I get an Expected Identifier error at the pinLocation line at the last square bracket for some reason.
I feel the solution is in the tip of my tongue. Just need that little extra push :)
Thanks guys
Just move the code inside the calloutAccessoryControlTapped method right after the line, wherever you have it, that creates the MKAnnotation. Give MKAnnotation subclass a float distance property and set it as the subtitle.

iOS MapKit show nearest annotations within certain distance

Currently i am working on a Location based application for iPhone/iPad . I have several annotations in my MapKit , what i want to do is to track the location of the user and shows the annotations that are within the 3km . Can somebody give me a start ?
Sorry for the delayed response... the question just fell off my radar.
I'm going to suppose that you have a method that returns a set of NSValue-wrapped CLLocationCoordinate2D structs (the basic approach is the same regardless of what your internal data representations are). You can then filter the list using a method something akin to the following (warning: typed in browser):
NSSet *locations = ...;
CLLocation centerLocation = ...; // Reference location for comparison, maybe from CLLocationManager
CLLocationDistance radius = 3000.; // Radius in meters
NSSet *nearbyLocations = [locations objectsPassingTest:^(id obj, BOOL *stop) {
CLLocationCoordinate2D testCoordinate;
[obj getValue:&testCoordinate];
CLLocation *testLocation = [[CLLocation alloc] initWithLatitude:testCoordinate.latitude
longitude:testCoordinate.longitude];
BOOL returnValue = ([centerLocation distanceFromLocation:testLocation] <= radius);
[testLocation release];
return returnValue;
}
];
With the filtered set of coordinates in hand, you can create MKAnnotation instances and add them to the map in the usual manner, as described in Apple's documentation.
If you have many thousands of test locations then I suppose this approach could start to incur performance issues. You would then want to switch your point storage approach to use, e.g., quadtrees, to reduce the number of points that need to be precision-filtered. But don't optimize prematurely!
Hope that helps!

MKMapview annotations go wrong after zooming in and zooming out

I'm using MKMapView with many annotations. Everything is OK until i zoom in and zoom out my map. Some annotations' location switch with each others. Anyone know why?
Here are my code that i call after viewDidLoad to import annotations from 3 arrays: longitudes, latitudes and photoFileName.
photoFileName contains photo file names for all annotations, longitudes and latitudes contain coordinates of them.
for (int i=0; i<[longitudes count]; i++)
CLLocation* location = [[CLLocation alloc] initWithLatitude:[latitudes objectAtIndex:i]
longitude:[longitudes objectAtIndex:i]];
CSMapAnnotation* annotation = [[CSMapAnnotation alloc] initWithCoordinate:[location coordinate]
annotationType:CSMapAnnotationTypeImage
title:#"";
subtitle:#"";
// Set data for the annotation. This data is used for displaying annotation
[annotation setUserData:[photoFilename objectAtIndex:i]];
[_mapView addAnnotation:annotation];
[annotation release];
[currentLocation release];
}
Everything loaded OK, scrolling and zooming worked. However, when I zoom in mapview (about 5-10 times bigger), and after that, zoom out again to the first size, some annotations locations are changed (See the bottom annotation).
I don't post the function viewForAnnotation here because it's not called, i just zoom in/out and it happends.
http://cC8.upanh.com/27.800.35078007.mF80/1.png http://cC9.upanh.com/27.800.35078008.AWG0/2.png
Oh, you are right. I solved problem after see again viewForAnnotation function. In my old code, when calling [mapView dequeueReusableAnnotationViewWithIdentifier:identifier], i just set identifier to "Image" for all annotations. It makes this function get a random annotation each time. Solved by specifying the true identifier for each annotation. Thanks for your warning!

MKMapView returns the wrong latitudeDelta and longitudeDelta in regionDidChangeAnimated

I'm attempting to talk to a web service for locations within the zoomed-in or zoomed-out area on an embedded MKMapView. In the regionDidChangeAnimated method of my view controller (this is the method that I use to trap any user gesture on the map), I call the following:
NSLog( #"latitude delta = %f", mapView.region.span.latitudeDelta );
NSLog( #"longitude delta = %f", mapView.region.span.longitudeDelta );
And the log entry says:
latitude delta = 0.000435
longitude delta = 0.001930
However, if I requery the lat/long dela manually after the regionDidChangeAnimated has fired (i.e. the user gesture is completed), I get the following:
latitude delta = 0.008415
longitude delta = 0.011932
Why is there a difference here? It doesn't matter whether this is a zoom in or zoom out gesture. There is always a difference. What's up with this??
I submitted this bug to Apple, and was informed that this is a known issue. No workaround was suggested. :-(

How do I determine if a coordinate is in the currently visible map region?

I have a list of several hundred locations and only want to display an MKPinAnnotation for those locations currently on the screen. The screen starts with the user's current location within a 2-mile radius. Of course, the user can scroll, and zoom on the screen. Right now, I wait for a map update event, then loop through my location list, and check the coordinates like this:
-(void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {
CGPoint point;
CLLocationCoordinate2D coordinate;
. . .
/* in location loop */
coordinate.latitude = [nextLocation getLatitude];
coordinate.longitude = [nextLocation getLongitude];
/* Determine if point is in view. Is there a better way then this? */
point = [mapView convertCoordinate:coordinate toPointToView:nil];
if( (point.x > 0) && (point.y>0) ) {
/* Add coordinate to array that is later added to mapView */
}
So I am asking to convert the coordinate where the point would be on the screen(unless I misunderstand this method which is very possible). If the coordinate isn't on the screen, then I never add it to the mapView.
So my question is, is this the correct way to determine if a location's lat/long would appear in the current view and should be added to the mapView? Or should I be doing this in a different way?
In your code, you should pass a view for the toPointToView: option. I gave it my mapView. You have to specify an upper bound for the x and y too.
Here's some code which worked for me (told me the currently visible annotations on my map, while looping through the annotation):
for (Shop *shop in self.shops) {
ShopAnnotation *ann = [ShopAnnotation annotationWithShop:shop];
[self.mapView addAnnotation:ann];
CGPoint annPoint = [self.mapView convertCoordinate:ann.coordinate
toPointToView:self.mapView];
if (annPoint.x > 0.0 && annPoint.y > 0.0 &&
annPoint.x < self.mapView.frame.size.width &&
annPoint.y < self.mapView.frame.size.height) {
NSLog(#"%# Coordinate: %f %f", ann.title, annPoint.x, annPoint.y);
}
}
I know this is an old thread, not sure what was available back then... But you should rather do:
// -- Your previous code and CLLocationCoordinate2D init --
MKMapRect visibleRect = [mapView visibleMapRect];
if(MKMapRectContainsPoint(visibleRect, MKMapPointForCoordinate(coordinate))) {
// Do your stuff
}
No need to convert back to the screen space.
Also I am not sure the reason why you are trying to do this, I think this is strange to not add annotations when they are not on the screen... MapKit already optimizes this and only creates (and recycles) annotation views that are visible.
After a little bit of reading I can't find anything that says this is a bad idea. I've done a bit of testing in my app and I always get correct results. The app loads much quicker when I only add coordinates that will show up in the currently visible map region instead of all the 300+ coordinates at once.
What I was looking for was a method like [mapView isCoordinateInVisibleRegion:myCoordinate], but so far this method is quick and seems accurate.
I've also changed the title to read "in the visible map region" instead of the previous because I think the incorrect title may have confused my meaning.