Best way to remove some annotations from an MKMapView - ios

To remove some annotations from an MKMapView, but not all of them, based on some condition, there seems to be 3 ways. I'd like to know which is the best one, and if there are any pitfalls with any of them. Thank you.
First way: removing annotations from annotations directly
for (id<MKAnnotation> annotation in self.mapView.annotations) {
if ([annotation isKindOfClass:[PinAnnotation class]]) {
[self.mapView removeAnnotation:annotation];
}
}
As the docs say, the mapView annotations property is readonly. So I assume it's a copy that I can safely manipulate.
Documentation: #property(nonatomic, readonly) NSArray <id<MKAnnotation>> *annotations
Second way: adding the unwanted annotation to an array first
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];
This code is copied from the example found here
It seems safer, but there's the overhead of creating the mutable array. If it's not necessary, I'd rather avoid it.
Third way : filtering the array
[_mapView.annotations filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"!(self isKindOfClass: %#)", [MKUserLocation class]]];
(answer found here: https://stackoverflow.com/a/2915063/873436).
I didn't try out this one, but it seems rather elegant and powerful.
What's the best way?
Is it dangerous to remove annotations while itering through them?
Thank you for your knowledge and insights!

Related

Remove dot/double dot from path in iOS

All single dot components of the path must be removed.
For example, "abi/./bune" should be normalized to "abi/bune".
All double dot components of the path must be removed, along with their parent directory. For example, "abi/ba/../bune" should be normalized to "abi/bune".
without using regular expressions. Any idea how to achieve?
This can be achieved by calling
NSString *standardizedPath = [path stringByStandardizingPath];
For example:
"/usr/bin/./grep" -> "/usr/bin/grep"
"/usr/include/objc/.." -> "/usr/include"
Worth noticing is that this also removes any initial components of /private and tries to expand any initial tilde expressions using stringByExpandingTildeInPath.
#Daniel's answer is the correct one. But since there was a discussion in the comments to the question, I decided to provide a code example for # Rey Gonzales's idea of tokenizing a string and using a stack.
WARNING: this code is here for purely educational purposes (because someone asked for it in the comments to the question). In real life stick to the #Daniel's solution.
The code might look like this:
-(NSString *)normalizePath:(NSString *)path {
NSArray *pathComponents = [path componentsSeparatedByString:#"/"];
NSMutableArray *stack = [[NSMutableArray alloc] initWithCapacity:pathComponents.count];
for (NSString *pathComponent in pathComponents) {
if ([pathComponent isEqualToString:#".."]) {
[stack removeLastObject];
}
else {
if (![pathComponent isEqualToString:#"."]) {
[stack addObject:pathComponent];
}
}
}
return [stack componentsJoinedByString:#"/"];
}

Constructing a NSArray of UIView objects with unique center

I have a array of custom UIView objects with 2 or more objects having same center and I have to construct another array from it with distinct centers. What is the best way to do it?
I tried with below piece of code but it does not work.
self.distinctObjects = [NSMutableArray arrayWithCapacity:iAllObjects.count];
for (MyCustomView *customView in iAllObjects)
{
BOOL hasDuplicate = [[self.distinctObjects filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF.center == %#", customView.center]] count] > 0;
if (!hasDuplicate) {
[self.distinctObjects addObject:customView];
}
}
You can't use struct's (in your case center is a CGPoint) in NSPredicate. Also comparing floating-point values directly isn't a good idea.
You should follow this answer. Just replace [annotation coordinate].latitude with myView.center.x, [annotation coordinate].longitude with myView.center.y, and so on. It should be easy.
BTW your code has O(n^2) complexity, but maybe that's not a problem if the array is small.
I fixed this with below piece of code:
NSMutableArray *uniqueCenters = [NSMutableArray arrayWithCapacity:iAllObjects.count];
self.distinctObjects = [NSMutableArray arrayWithCapacity:iAllObjects.count];
for (MyCustomView *customView in iAllObjects)
{
if (![uniqueCenters containsObject:[NSValue valueWithCGPoint:customView.center]]) {
[uniqueCenters addObject:[NSValue valueWithCGPoint:customView.center]];
[self.beacons addObject:customView];
}
}

ViewForAnnotation work in iPad 6.0 simulator but doesn't work in iPad 5.1 simulator

I started to build an app with iOS6 that worked great, but then for reasons of force grater, I had to switch to IOS5. However, there is a map that keeps giving me problems. This map has many types of annotationView (for example cinemas, restaurants, theaters ....), each with their own image. When I passed from iOS6 to iOS5 I noticed that the annotationView do not behave the same as before because the call of the delegate method to construct them is no longer the same. What can I do?
-(void)viewDidLoad{
//.....
//extraction of elements from the tables in a database
for(int i=0; i<7; i++){ //extraction tables from database
//......
for (int i =0; i< number; i++) { //extraction elements from tables
self.chinaTable =[self.mutableArray objectAtIndex:i];
CLLocationCoordinate2D coord=CLLocationCoordinate2DMake(self.chinaTable.latitudine, self.chinaTable.longitudine);
AnnotationCustom *annotationIcone =[[AnnotationCustom alloc]initWithCoordinates:coord title:self.chinaTable.titolo subTitle:self.chinaTable.indirizzo];
//.......
[self.mapView addAnnotation:annotation];
self.locationManager.delegate = self;
self.mapView.delegate=self; //the problem is here
}
}
The delegate method
- (MKAnnotationView *)mapView:(MKMapView *)mapview viewForAnnotation:(id
<MKAnnotation>)annotation
{
NSLog (#"the name of the table is %#", self.nomeTabella);
// this NSLog you get only the name of the last open table
//..........
if ([annotation isKindOfClass:[AnnotationCustom class]]){
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKAnnotationView *annotationView = (MKAnnotationView*) [mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
annotationView = [[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
annotationView.annotation = annotation;
AnnotationCustom *customAnnotation = (AnnotationCustom *)annotationView.annotation;
//I create an annotation different depending on the name of the table of provenance
if ([self.nomeTabella isEqualToString:#"Cinema"]){
if(UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPhone){
annotationView.image = [UIImage imageNamed:#"iphone_cinema.png"];
customAnnotation.nomeTabella = self.nomeTabella;
customAnnotation.ID = self.chinaTable.ID;
customAnnotation.china = self.chinaTable;
}else{
annotationView.image = [UIImage imageNamed:#"ipad_cinema.png"];
customAnnotation.nomeTabella = self.nomeTabella;
customAnnotation.ID = self.chinaTable.ID;
customAnnotation.china=self.chinaTable;
}
//......
}
The delegate method viewForAnnotation is no longer being called after the construction of each annotation, but is only called at the end of both cycles, accordingly annotationView on the map are only those of the last table in memory. Where can I get set i delegate methods to get the same result as before? ViewForAnnotation work fine in iPad 6.0 simulator but doesn't work in iPad simulator 5.1 and the code is the same
I'm surprised to hear that viewForAnnotation was only ever called after each construction. It's not the way it is documented and it is not the way it is used by anyone else. viewForAnnotation can and will be called at any point int your app. If the user scrolls the map so some annotations disappear it can be called when they scroll the map back and they annotations reappear. Or if the user switches to another view or app and then comes back.
You need to make viewForAnnotation check the annotation variable for some property that lets you determine how to draw that view. You can't rely on it being called in any particular order. There's plenty of sample code around that will show you how to implement the MKAnnotation protocol with your own class and you can add something to that class to tell you if the thing it represents is a cinema or restaurant or whatever, then you fetch the right image put it into the MKAnnotationView that viewForAnnotation wants as a return value.
viewForAnnotation isn't being called when you are running through your loops (i.e. as you add each MKAnnotation) because you are on the main runLoop. When your viewDidLoad finishes and a runLoop cycle occurs the MKAnnotations (those on the current map location) will be drawn on the screen, thus calling out to your delegate. So while you are debugging and stepping through viewDidLoad it is perfectly normal that you are not seeing calls to the delegate methods.
A couple of things:
You should be setting your delegate methods outside of the loops. so self.locationManager.delegate and self.mapView.delegate should be set before your for loops, not a million times inside your nested loops.
You are reusing i as your for...loop variable. I'm hoping this is just caused by you removing business logic for posting here, and not in your actual code. It would explain why only your last set of annotations appears to be rendered.
Are you sure you aren't manipulating the mapView.annotations elsewhere in the nested for...loops? Check for removeAnnotations and setAnnotations: both could cause the annotations in the mapView to be different from what you'd expect.
Can you show how your annotation object is constructed? Another possibility is that you're accidentally mutating existing objects instead of creating new ones.
your code seems ok to me. Actually I'm also developing an Iphone app using Maps and I'm doing it like you. Only one question, why are you using:
if(UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPhone)
is it to check if user is running an iPad or an iPhone?

Enabling and Disabling Annotation Dragging (On The Fly) (iOS Mapkit)

I've created a mapview which has a button to switch between an 'edit' mode and a 'drag' mode based on the project requirements. I realize that it's easy enough to have your annotations draggable from creation by setting them draggable in the viewForAnnotation, but the behavior required won't allow this. I've tried a couple different ways of changing the annotations to draggable without success. The first thought was to loop through the existing annotations and set each one to 'draggable' and 'selected', but I get an unrecognized selector sent to instance error (I did try instantiating a new annotation to pass in the object and re-plot while in the loop, but I get the same error as well):
NSLog(#"Array Size: %#", [NSString stringWithFormat:#"%i", [mapView.annotations count]]);
for(int index = 0; index < [mapView.annotations count]; index++) {
if([[mapView.annotations objectAtIndex:index]isKindOfClass:[locAnno class]]){
NSLog(#"** Location Annotation at Index: %#", [NSString stringWithFormat:#"%i", index]);
NSLog(#"* Location Marker: %#", [mapView.annotations objectAtIndex:index]);
}
if([[mapView.annotations objectAtIndex:index]isKindOfClass:[hydAnno class]]) {
NSLog(#"** Hydrant Annotation at Index: %#", [NSString stringWithFormat:#"%i", index]);
NSLog(#"* Hydrant Marker: %#", [mapView.annotations objectAtIndex:index]);
[[mapView.annotations objectAtIndex:index]setSelected:YES];
[[mapView.annotations objectAtIndex:index]setDraggable:YES];
}
}
The second thought was to use 'didSelectAnnotationView', and set selected and draggable on the annotation when it's selected, and reset the properties when the mode switches back again. This works, but very poorly as the event doesn't always fire and your left to tap the annotation one or more times before it will change the properties:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
NSLog(#"Annotation Selected!");
if(!editMode) {
view.selected = YES;
view.draggable = YES;
}
}
The first attempt seems the most simple solution if I can get it to work. Using the didSelect method on the other hand is cumbersome and hack-licious. I'm quite new to iOS development, so I apologize if I've overlooked something novice while hammering away at this. I appreciate any insight the community can offer. Thanks much.
The first method is better than using the didSelectAnnotationView delegate method.
The problem with the code which causes the "unrecognized selector" error is that it is calling setSelected: and setDraggable: on the annotation objects (type id<MKAnnotation>) instead of their corresponding MKAnnotationView objects. The id<MKAnnotation> objects don't have such methods so you get that "unrecognized selector" error.
The map view's annotations array contains references to the id<MKAnnotation> (data model) objects -- not the MKAnnotationView objects for those annotations.
So you need to change this:
[[mapView.annotations objectAtIndex:index]setSelected:YES];
[[mapView.annotations objectAtIndex:index]setDraggable:YES];
to something like this:
//Declare a short-named local var to refer to the current annotation...
id<MKAnnotation> ann = [mapView.annotations objectAtIndex:index];
//MKAnnotationView has a "selected" property but the docs say not to set
//it directly. Instead, call deselectAnnotation on the annotation...
[mapView deselectAnnotation:ann animated:NO];
//To update the draggable property on the annotation view, get the
//annotation's current view using the viewForAnnotation method...
MKAnnotationView *av = [mapView viewForAnnotation:ann];
av.draggable = editMode;
You must also update the code in the viewForAnnotation delegate method so that it also sets draggable to editMode instead of a hard-coded YES or NO so that if the map view needs to re-create the view for the annotation after you've already updated it in the for-loop, the annotation view will have the right value for draggable.

How to load only few annotations around current location?

I read through many topic but i don't think i really get them.
I found the following code in one of the topic but I am not sure how to put it. Should i put in viewdidload? or somewhere else?
And I have a large annotation data of 3000 dots. But I dont know how to read a sqlite data (I am using a plist data) Please give me a help.
Thanks in advance.
// create and populate an array containing all potential annotations
NSMutableArray *allPotentialAnnotations = [NSMutableArray array];
for(all potential annotations)
{
MyAnnotation *myannotation = [[MyAnnotation alloc]
initWithCoordinate:...whatever...];
[allPotentialAnnotations addObject:myannotation];
[myannotation release];
}
// set the user's current location as the reference location
[allPotentialAnnotations
makeObjectsPerformSelector:#selector(setReferenceLocation:)
withObject:mapView.userLocation.location];
// sort the array based on distance from the reference location, by
// utilising the getter for 'distanceFromReferenceLocation' defined
// on each annotation (note that the factory method on NSSortDescriptor
// was introduced in iOS 4.0; use an explicit alloc, init, autorelease
// if you're aiming earlier)
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor
sortDescriptorWithKey:#"distanceFromReferenceLocation"
ascending:YES];
[allPotentialAnnotations sortUsingDescriptors:
[NSArray arrayWithObject:sortDescriptor]];
// remove extra annotations if there are more than five
if([allPotentialAnnotations count] > 5)
{
[allPotentialAnnotations
removeObjectsInRange:NSMakeRange(5,
[allPotentialAnnotations count] - 5)];
}
// and, finally, pass on to the MKMapView
[mapView addAnnotations:allPotentialAnnotations];
probably the first thing to do is clustering your data on the map. that means combining several points to one point on the map. the more the user zoomes in the more the clusters will be resolved to single points placed on the map.
there are some recommendations how to do that in this post:
How can I reduce the number of annotations on a map?
the next step is to itterate through your data and check if one point is within the region arround the user location. you can get the GEO position of the visible display area like this:
CLLocationCoordinate2d topLeft, bottomRight;
topLeft = [mapView convertPoint:CGPointMake(0, 0) toCoordinateFromView:mapView];
CGPoint pointBottomRight = CGPointMake(mapView.frame.size.width, mapView.frame.size.height);
bottomRight = [mapView convertPoint:pointBottomRight toCoordinateFromView:mapView];

Resources