Slow down when dealing with MKAnnotations on MKMapView - iphone

I'm stepping thru the annotations on my map, in order to find one which has a certain tag. I then remove this annotation, and add a new one (a diff color)
The problem is that this takes a very, very long time. Not sure why. It's going thru 300 annotations, and it takes up to 20 seconds! How can I speed this up? It there a way to, for instance, only get the annotations which are currently visible? Or, if I have the annotation, can I tell what is its index in mapView.annotations array is, so I don't have to step thru?
for (int i =0; i < [self.mapView.annotations count]; i++)
{
if ([[self.mapView.annotations objectAtIndex:i] isKindOfClass:[BasicMapAnnotation class]] )
{
BasicMapAnnotation *bmaTemp =(BasicMapAnnotation*)[self.mapView.annotations objectAtIndex:i];
if (bmaTemp.mapTag == pinTag) {
[self.mapView removeAnnotation:[self.mapView.annotations objectAtIndex:i]];
self.customAnnotation = [[[BasicMapAnnotation alloc] initWithLatitude:[shop.latitude doubleValue] andLongitude:[shop.longitude doubleValue]] autorelease];
self.customAnnotation.mapTag = pinTag;
[self.mapView addAnnotation:self.customAnnotation];
}
}
}

If you are sure that the Annotation you want to modify is actually visible, you could use something like
for (id annotation in [self.mapView annotationsInMapRect:self.mapView.visibleMapRect]){
if([annotation isKindOfClass:[BasicMapAnnotation class]]){
BasicMapAnnotation *annotationToModify = annotation;
if (bmaTemp.mapTag == pinTag) {
[self.mapView removeAnnotation:annotationToModify];
self.customAnnotation = [[[BasicMapAnnotation alloc] initWithLatitude:[shop.latitude doubleValue] andLongitude:[shop.longitude doubleValue]] autorelease];
self.customAnnotation.mapTag = pinTag;
[self.mapView addAnnotation:self.customAnnotation];
}
}
}
If you want to know the index of a single given annotation:
int annotationIndex = [self.mapView.annotations indexOfObject:givenAnnotation];
Untested, I wrote this here on SO so please let me know, whether it works or not.

Related

MKPolygon ontouch detailview

I've created a MKMapView with MKPolygons based on coordinates. There are multiple polygons on the map (look here for an example of what I am re-creating as an app).
What I am trying to do is when the user touches the polygon, it opens a popover view with information about the location. This information is currently stored inside a plist file with the coordinates.
What I currently have so far is that I am able to get touch event and print to the log that the polygon was touched.
The question that I have is:
Can MKPolygonView be used like an MKAnnotationView where once the user taps the pin more information pops up about that current location?
I want to do the same for the polygon view. When touched, the user would see more information about the location that is stored in the plist. If it is possible what would be the best way to get it to work?
My current code is below.
#import "outagemapViewController.h"
#import "MyAnnotation.h"
#import "WildcardGestureRecognizer.h"
#define METERS_PER_MILE 46309.344
#interface outagemapViewController ()
#end
#implementation outagemapViewController
- (void)viewDidLoad {
outages = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"outages"ofType:#"plist"]];
for (NSDictionary *coloredAreas in outages) {
coordinateData = coloredAreas[#"coords"];
test = coloredAreas[#"outages"];
NSLog(#"test %#", test);
coordsLen = [coordinateData count];
NSLog(#"coords %d", coordsLen);
CLLocationCoordinate2D coords[coordsLen];
for (i=0; i < coordsLen; i++) {
NSString *lat = coordinateData[i];
NSArray *latt = [lat componentsSeparatedByString:#","];
double latitude = [[latt objectAtIndex:0] doubleValue];
double longitude = [[latt objectAtIndex:1] doubleValue];
coords[i] = CLLocationCoordinate2DMake(latitude, longitude);
}
MKPolygon* poly2 = [MKPolygon polygonWithCoordinates:coords count:coordsLen];
poly2.title=#"test";
[self.mapView addOverlay:poly2];
}
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonView* aView = [[MKPolygonView alloc] initWithPolygon:(MKPolygon*)overlay];
int numbers = [test intValue];
if(numbers >= 10){
aView.fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.6];
aView.strokeColor = [[UIColor greenColor] colorWithAlphaComponent:1.0];
aView.lineWidth = 3;
}else if(numbers < 10){
aView.fillColor = [[UIColor yellowColor] colorWithAlphaComponent:0.6];
aView.strokeColor = [[UIColor yellowColor] colorWithAlphaComponent:1.0];
aView.lineWidth = 3;
}
return aView;
}
return nil;
}
}
-(void)viewWillAppear:(BOOL)animated{
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 35.20418;
zoomLocation.longitude = -89.86862;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE);
[_mapView setRegion:viewRegion animated:YES];
WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.mapView];
CLLocationCoordinate2D coord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in self.mapView.overlays)
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygon *poly = (MKPolygon*) overlay;
id view = [self.mapView viewForOverlay:poly];
if ([view isKindOfClass:[MKPolygonView class]])
{
MKPolygonView *polyView = (MKPolygonView*) view;
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon) {
// debug(#"hit!");
NSLog(#"hit");
} else {
NSLog(#"miss");
}
}
}
}
};
[self.mapView addGestureRecognizer:tapInterceptor];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Unfortunately, for overlays, there's no built-in touch-detection and callout view like there is for annotations.
You'll have to do the touch-detection manually like you're already doing (and it looks like it should work).
(Even more unfortunate here is that adding a gesture recognizer directly to the overlay view doesn't work -- you have to add it to the whole map and then check whether the touch point is in any overlay.)
For an overlay callout view, once you've detected a touch on an overlay, you can create a custom UIView and do addSubview. I suggest adding it to the map instead of the overlay view and you might be able to use the CGPoint point you are already calculating to determine the frame of the custom callout view.
You might also want to keep a ivar/property reference to the overlay callout view so it can be easily removed and re-added if the user taps on another overlay while the callout for another overlay is already displayed.
Another option which is probably easier is to create a custom UIViewController and present or push it. The specifics of showing it depend on whether you're using a navigation controller and/or storyboard.
If your app is also built for iPad, you could also show the "callout" using a UIPopoverController.
See How do I display a UIPopoverView as a annotation to the map view? (iPad) for a code example (it's with an annotation but you should be able to adapt it for the overlay).
Once you've identified which overlay was tapped, you need to display its associated data which is in your original data source (the outages array). Right now, overlays are created and added but have no reference back to the original data object (outage dictionary in outages array).
(Subclassing MKPolygon to add a custom property has issues and workarounds and creating a completely custom MKOverlay class introduces a lot of other additional work.)
For your current data source structure, a simple, quick (and somewhat crude) option is to set the overlay's title property to the index in the outages array of the outage object associated with the overlay. Since the title property is an NSString and the array index is an integer, we'll convert it to a string:
NSUInteger outageIndex = [outages indexOfObject:coloredAreas];
poly2.title = [NSString stringWithFormat:#"%d", outageIndex];
[self.mapView addOverlay:poly2];
In viewForOverlay, it looks like you're using test (which comes from an outage object) to determine the polygon's color. The value of the externally declared/set test variable will not necessarily be in sync with the overlay the delegate method is currently being called for (the map could call viewForOverlay multiple times for the same overlay and not necessarily in the order you add them). You have to retrieve the outage object based on some property of the overlay parameter. Since we are setting the overlay's title property to the outage's index:
//int numbers = [test intValue]; <-- remove this line
int outageIndex = [overlay.title intValue];
NSDictionary *outageDict = [outages objectAtIndex:outageIndex];
id outageNumbersObject = outageDict[#"outages"];
//replace id above with actual type
//can't tell from code in question whether it's NSString or NSNumber
int numbers = [outageNumbersObject intValue];
//use "numbers" to set polygon color...
Finally, when an overlay is tapped, you use the same method as in viewForOverlay to get the outage object:
if (mapCoordinateIsInPolygon) {
int outageIndex = [overlay.title intValue];
NSDictionary *outageDict = [outages objectAtIndex:outageIndex];
NSLog(#"hit, outageDict = %#", outageDict);
//show view with info from outageDict...
}

Centralized the code

- (void) setTheMap
{
myMap.mapType = MKMapTypeStandard;
[myMap setDelegate:self];
CLLocationCoordinate2D coord = {latitude:myValue , longitude:myValue };
MKCoordinateSpan span = {latitudeDelta:0.05f , longitudeDelta: 0.05f};
MKCoordinateRegion region = {coord , span};
[myMap setRegion:region];
[self.view addSubview:myMap];
PantryAnnotation *dis = [[PantryAnnotation alloc] init];
dis.coordinate = region.center;
dis.title = row.panName;
dis.subTitle = row.geo_lat;
[myMap addAnnotation:dis];
}
- (MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id<MKAnnotation>)annotation
{ MKPinAnnotationView *pview = nil;
if (annotation != myMap.userLocation)
{
static NSString *defalt = #"Volunteer";
pview = (MKPinAnnotationView *)[myMap dequeueReusableAnnotationViewWithIdentifier:defalt];
if ( pview == nil )
pview = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defalt] autorelease];
pview.pinColor = MKPinAnnotationColorRed;
pview.canShowCallout = YES;
pview.animatesDrop = YES;
}
else
{
[myMap.userLocation setTitle:#"I am Here"];
}
return pview;
}
The above gives me map view with annotation pin. I have to write this code in more than 8 files. Can any one tell me how can I centralized this code ? I mean to create only one class and by object of that class I can add the map in any other view.
Thanks..
Since the rest of your code is not clear, it is hard to give advice, but you may try to analyze it first;
what is common in these 8 code parts, and what is different?
Then you should figure out if you need to create and hold references to any local variables, lets say your map objects, or something like that.
If so, then subclassing would be the first choice to look at,
if not then an Obj-C category would probably do (http://macdevelopertips.com/objective-c/objective-c-categories.html). P.S: you cannot add member fields with this, only behavior (methods) are allowed.
If you need only objects with little to none behavior other than these, then you may make them plain objects holding the parameters only.
Or even you may use static methods like ( +(void) blahblah; ) just to be short, but dont do it unless you are sure that the behavior is generic in nature...
Well, a soup of advice here, if you provide more info, i can write in one of these directions in more details
good luck!

Trying to Show User Location on Mapkit, the most annoying thing ever. Anyone lend a hand?

I'm following this tutorial (http://icodeblog.com/2009/12/21/introduction-to-mapkit-in-iphone-os-3-0/) on adding mapkit and annotations to an application. However, i'm seriously struggling with the User Location. I'm new to xcode so not quite sure what to do next. I have tried Tony's option:
step one: add the CoreLocation framework to the project.
Step two: add this function to the iCodeMapViewController.m:
- (void)setCurrentLocation:(CLLocation *)location {
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center = location.coordinate;
region.span.longitudeDelta = 0.15f;
region.span.latitudeDelta = 0.15f;
[self.mapView setRegion:region animated:YES];
}
step three: add this code to the ViewForAnnotation Method:
if (annotation != mapView.userLocation) {
//the rest of the ViewForAnnotation code goes here
}else{
CLLocation *location = [[CLLocation alloc]
initWithLatitude:annotation.coordinate.latitude
longitude:annotation.coordinate.longitude];
[self setCurrentLocation:location];
}
But when i go to build, it doesn't like it.
I've also tried this option:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation
{
if ([annotation isKindOfClass:MKUserLocation.class]) return nil;
//rest of code
}
The blue dot shows, my custom annotations show but the app crashes when i try and scroll through the table. The debugger gives no help but does stop on this statement.
Can someone please help? With code examples too? i think the answer to this post might be useful to a number of people also struggling with the mapkit.
Cheers
I had the same problem, but I managed to solve it.
In cellForRowAtIndexPath I did this:
NSMutableArray *annotations = [[NSMutableArray alloc] init];
if(indexPath.section == 0)
{
for(MinuAsukohad *annotation in [mapView annotations])
{
if(![annotation isKindOfClass:[MKUserLocation class]])
{
if([annotation annotationType] == MinuAsukohadTypeInterest)
{
[annotations addObject:annotation];
}
}
}
cell.textLabel.text = [[annotations objectAtIndex:indexPath.row] title];
}
You just have to repeat it for all the sections.
Sounds like you are trying to include your current location as one of the cells in the table ... look at your console and give us the output when the crash happens.

iPhone MapKit - update annotations coordinates and map

I use this tutorial for integrating MapKit to my application:
http://iphonebcit.wordpress.com/iphone-map-kit-tutorial/
CLLocationCoordinate2D coordinate;
coordinate.latitude = 49.2802;
coordinate.longitude = -123.1182;
NSUInteger count = 1;
for(int i = 0; i < 10; i++) {
CGFloat latDelta = rand()*.035/RAND_MAX - .02;
CGFloat longDelta = rand()*.03/RAND_MAX - .015;
CLLocationCoordinate2D newCoord = {coordinate.latitude+latDelta, coordinate.longitude+longDelta};
MapDemoAnnotation* annotation = [[MapDemoAnnotation alloc] initWithCoordinate:newCoord andID:count++];
[mapView addAnnotation:annotation];
[annotation release];
}
and
- (MKAnnotationView *)mapView:(MKMapView *)mapViewLocal viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = (MKPinAnnotationView*)[mapViewLocal dequeueReusableAnnotationViewWithIdentifier:#"Pin"];
if(pinView == nil) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Pin"];
pinView.pinColor = MKPinAnnotationColorPurple;
pinView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.animatesDrop = YES;
pinView.canShowCallout = YES;
} else {
pinView.annotation = annotation;
}
return pinView;
}
So the pins will set on the map randomly. In my application, the coordinates will change. How can I change the coordinates of the annotations, so they will be updated on the map?
Does anyone know?
At iPhone SDK 3.x you have to remove the pin annotations and set it again. That is not very nice if you have many annotations an your map.
I try to make it better so I ony display/renew my pin annotations which are on the screen. So if an user zoom in to New York, there won't be pin annotations in San Francisco or other than the user can't see. So the performance will be much better.
Perhaps in the future this would be possible. I hope so :-)
That tutorial is only for getting a map view to show, nothing else. You're going to need a bigger tutorial. I found this one useful:
http://blog.objectgraph.com/index.php/2009/04/02/iphone-sdk-30-playing-with-map-kit/
Probably what you'll need to do is to loop through all the annotations on the map, removing them, then loop through your data array, adding the annotations back again. You could be more clever about it and loop through the data array, checking if there's already a pin with the same latitude/longitude on the map, but that way gets more complicated.
Since I'm doing annotations at the moment I just made a quick test. You do get a compiler warning, so it might not be supported. But it works.
Make a custom MKAnnotation class so you can set the coordinate property to be writable:
#property (nonatomic, readwrite) CLLocationCoordinate2D coordinate;
Then at whatever event or interval you want, change the coordinate using something like this:
CLLocation *loc=[[CLLocation alloc] initWithLatitude:55.0 longitude:17.0];
annotation.coordinate=loc.coordinate;
[loc release];

How to remove all annotations from MKMapView without removing the blue dot?

I would like to remove all annotations from my mapview without the blue dot of my position. When I call:
[mapView removeAnnotations:mapView.annotations];
all annotations are removed.
In which way can I check (like a for loop on all the annotations) if the annotation is not the blue dot annotation?
EDIT (I've solved with this):
for (int i =0; i < [mapView.annotations count]; i++) {
if ([[mapView.annotations objectAtIndex:i] isKindOfClass:[MyAnnotationClass class]]) {
[mapView removeAnnotation:[mapView.annotations objectAtIndex:i]];
}
}
Looking at the MKMapView documentation, it seems like you have the annotations property to play with. It should be pretty simple to iterate through this and see what annotations you have :
for (id annotation in myMap.annotations) {
NSLog(#"%#", annotation);
}
You also have the userLocation property which gives you the annotation representing the user's location. If you go through the annotations and remember all of them which are not the user location, you can then remove them using the removeAnnotations: method :
NSInteger toRemoveCount = myMap.annotations.count;
NSMutableArray *toRemove = [NSMutableArray arrayWithCapacity:toRemoveCount];
for (id annotation in myMap.annotations)
if (annotation != myMap.userLocation)
[toRemove addObject:annotation];
[myMap removeAnnotations:toRemove];
Hope this helps,
Sam
If you like quick and simple, there's a way to filter an array of the MKUserLocation annotation. You can pass this into MKMapView's removeAnnotations: function.
[_mapView.annotations filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"!(self isKindOfClass: %#)", [MKUserLocation class]]];
I assume this is pretty much the same as the manual filters posted above, except using a predicate to do the dirty work.
Isn't it easier to just do the following:
//copy your annotations to an array
NSMutableArray *annotationsToRemove = [[NSMutableArray alloc] initWithArray: mapView.annotations];
//Remove the object userlocation
[annotationsToRemove removeObject: mapView.userLocation];
//Remove all annotations in the array from the mapView
[mapView removeAnnotations: annotationsToRemove];
[annotationsToRemove release];
shortest way to clean all annotations and preserving MKUserLocation class annotation
[self.mapView removeAnnotations:self.mapView.annotations];
for (id annotation in map.annotations) {
NSLog(#"annotation %#", annotation);
if (![annotation isKindOfClass:[MKUserLocation class]]){
[map removeAnnotation:annotation];
}
}
i modified like this
it easier to just do the following:
NSMutableArray *annotationsToRemove = [NSMutableArray arrayWithCapacity:[self.mapView.annotations count]];
for (int i = 1; i < [self.mapView.annotations count]; i++) {
if ([[self.mapView.annotations objectAtIndex:i] isKindOfClass:[AddressAnnotation class]]) {
[annotationsToRemove addObject:[self.mapView.annotations objectAtIndex:i]];
[self.mapView removeAnnotations:annotationsToRemove];
}
}
[self.mapView removeAnnotations:annotationsToRemove];
For Swift 3.0
for annotation in self.mapView.annotations {
if let _ = annotation as? MKUserLocation {
// keep the user location
} else {
self.mapView.removeAnnotation(annotation)
}
}