Adding multiple annotations and removing only one specific annotation - ios

In my project I'm adding multiple annotations, but I need to remove only one annotation.
Is there any way I can specify an annotation?
I have tried to use dynamic variable during the creation, but it did not work.

This might be worked
-(void) mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
// Do other stuff
annotationToRemove = view.annotation;
}
You can also implement the didDeselectAnnotaionView method as well.
for reference check this Annotations

Related

Custom UserLocation annotation in MapBox

In the upcoming version 3.4 of the MapBox iOS SDK, it should be possible to create custom annotations for the user location.
In the regular Apple MapKit, we used to do this by creating an custom view in mapView: viewForAnnotation.
Is the approach to this different with MapBox? We identify the annotations by the class they are based on. I assume that we would expect this delegate method to be called also for class MGLUserLocation?
-(MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation
{
if ([annotation isKindOfClass:[MGLUserLocation class]])
{
NSLog(#"MGLUserLocation"); // No hit
}
return nil;
}

iOS MapKit dragged annotations (MKAnnotationView) no longer pan with map

I'm learning to use MapKit in my fledgling iOS app. I'm using some of my model entities as annotations (added the <MKAnnotation> protocol to their header file). I also create custom MKAnnotationViews and set the draggable property to YES.
My model object has a location property, which is a CLLocation*. To conform to the <MKAnnotation> protocol, I added the following to that object:
- (CLLocationCoordinate2D) coordinate {
return self.location.coordinate;
}
- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate {
CLLocation* newLocation = [[CLLocation alloc]
initWithCoordinate: newCoordinate
altitude: self.location.altitude
horizontalAccuracy: self.location.horizontalAccuracy
verticalAccuracy: self.location.verticalAccuracy
timestamp: nil];
self.location = newLocation;
}
- (NSString*) title {
return self.name;
}
- (NSString*) subtitle {
return self.serialID;
}
So, I have the 4 required methods. And they're pretty straightforward. When I read the apple docs on MKAnnotationView and the #draggable property, it says the following:
Setting this property to YES makes an annotation draggable by the user. If YES, the associated annotation object must also implement the setCoordinate: method. The default value of this property is NO.
And elsewhere, the MKAnnotation docs say:
Your implementation of this property must be key-value observing (KVO) compliant. For more information on how to implement support for KVO, see Key-Value Observing Programming Guide.
I have read that (brief) document, and it is not clear at all to me what I'm supposed to do to accomplish that so that the coordinate, which I'm deriving from my location property is a proper property in and of itself.
But I'm reasonably sure it's not working correctly. When I drag the pin, it moves, but then it no longer relocates when I pan the map.
UPDATE
So I tried playing with the stock MKPinAnnotationView. To do this, I simply commented out my delegate's mapView:viewForAnnotation: method. I discovered that these aren't draggable by default. I added the mapView:didAddAnnotationViews: to my delegate to set the draggable property of the added views to YES.
Once configured thus, the Pin views, as hinted by John Estropia below, seem to work fine. I decided to use the mapView:annotationView:didChangeDragState:fromOldState: delegate hook to get a closer look at what is going on:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState {
NSArray* states = #[#"None", #"Starting", #"Dragging", #"Cancelling", #"Ending"];
NSLog(#"dragStateChangeFrom: %# to: %#", states[oldState], states[newState]);
}
For the stock pins, one will see log output that looks like this:
2014-02-05 09:07:45.924 myValve[1781:60b] dragStateChangeFrom: None to: Starting
2014-02-05 09:07:46.249 myValve[1781:60b] dragStateChangeFrom: Starting to: Dragging
2014-02-05 09:07:47.601 myValve[1781:60b] dragStateChangeFrom: Dragging to: Ending
2014-02-05 09:07:48.006 myValve[1781:60b] dragStateChangeFrom: Ending to: None
Which looks pretty logical. But if you switch to the configured MKAnnotationView, the output you will see looks like:
2014-02-05 09:09:41.389 myValve[1791:60b] dragStateChangeFrom: None to: Starting
2014-02-05 09:09:45.451 myValve[1791:60b] dragStateChangeFrom: Starting to: Ending
It misses TWO transitions, from Starting to Dragging, and from Ending to None.
So I begin to be skeptical that I need to do something different with properties. But I'm still frustrated with why this won't work.
UPDATE 2
I created my own Annotation object to stand between my model objects, which could have a property coordinate property. The behavior remains the same. It seems to be something with the MKAnnotationView.
There are lots of examples about how to use the delegate method mapView:viewForAnnotation: that show setting up an MKAnnotationView. But what is not as obvious is that just because you set the draggable property of your MKAnnotationView instance to YES, you still have to write some code to help it transition some of the states. MapKit will take care of moving your instance's dragState to MKAnnotationViewDragStateStarting and MKAnnotationViewDragStateEnding, but it will not do the other transitions. You see hints of this in the docs notes about subclassing MKAnnotationView and the need to override the `setDragState:animated:'
When the drag state changes to MKAnnotationViewDragStateStarting, set the state to MKAnnotationViewDragStateDragging. If you perform an animation to indicate the beginning of a drag, and the animated parameter is YES, perform that animation before changing the state.
When the state changes to either MKAnnotationViewDragStateCanceling or MKAnnotationViewDragStateEnding, set the state to MKAnnotationViewDragStateNone. If you perform an animation at the end of a drag, and the animated parameter is YES, you should perform that animation before changing the state.
In this case, I'm not subclassing, but it seems that MKAnnotationView still struggles to make the transitions on its own. So you have to implement the delegate's mapView:annotationView:didChangeDragState:fromOldState: method. E.g.
- (void)mapView:(MKMapView *)mapView
annotationView:(MKAnnotationView *)annotationView
didChangeDragState:(MKAnnotationViewDragState)newState
fromOldState:(MKAnnotationViewDragState)oldState {
if (newState == MKAnnotationViewDragStateStarting) {
annotationView.dragState = MKAnnotationViewDragStateDragging;
}
else if (newState == MKAnnotationViewDragStateEnding || newState == MKAnnotationViewDragStateCanceling) {
annotationView.dragState = MKAnnotationViewDragStateNone;}
}
}
This allows things to complete appropriately, so that when you pan the map after dragging the annotation, the annotation moves with the pan.
Are you using a custom map pin? I saw this before as well. Seems to be a bug in iOS 7. As a workaround, we just ended up using the default pin for MKPinAnnotationView.
Your setCoordinate was missing the required Key-Value observing methods (willChangeValueForKey/didChangeValueForKey) that are required for the map to detect when the annotation has been moved so it can move the annotation view to match, e.g.
- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate {
[self willChangeValueForKey:#"coordinate"];
CLLocation* newLocation = [[CLLocation alloc]
initWithCoordinate: newCoordinate
altitude: self.location.altitude
horizontalAccuracy: self.location.horizontalAccuracy
verticalAccuracy: self.location.verticalAccuracy
timestamp: nil];
self.location = newLocation;
[self didChangeValueForKey:#"coordinate"];
}
Source:
https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKAnnotation_Protocol/index.html#//apple_ref/occ/intfp/MKAnnotation/coordinate
"Your implementation of this property must be key-value observing (KVO) compliant. For more information on how to implement support for KVO, see Key-Value Observing Programming Guide."
Sorry Apple wasn't clear in the docs, what they meant to say was the setCoordinate requires the Key-Value observing methods willChangeValueForKey & didChangeValueForKey that allow the map to detect when the annotation has been moved so it can move the annotation view to match, e.g.
- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate {
[self willChangeValueForKey:#"coordinate"];
CLLocation* newLocation = [[CLLocation alloc]
initWithCoordinate: newCoordinate
altitude: self.location.altitude
horizontalAccuracy: self.location.horizontalAccuracy
verticalAccuracy: self.location.verticalAccuracy
timestamp: nil];
self.location = newLocation;
[self didChangeValueForKey:#"coordinate"];
}

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.

Wanted: How to reliably, consistently select an MKMapView annotation

After calling MKMapView's setCenterCoordinate:animated: method (without animation), I'd like to call selectAnnotation:animated: (with animation) so that the annotation pops out from the newly-centered pushpin.
For now, I simply watch for mapViewDidFinishLoadingMap: and then select the annotation. However, this is problematic. For instance, this method isn't called when there's no need to load additional map data. In those cases, my annotation isn't selected. :(
Very well. I could call this immediately after setting the center coordinate instead. Ahh, but in that case it's possible that there is map data to load (but it hasn't finished loading yet). I'd risk calling it too soon, with the animation becoming spotty at best.
Thus, if I understand correctly, it's not a matter of knowing if my coordinate is visible, since it's possible to stray almost a screenful of distance and have to load new map data. Rather, it's a matter of knowing if new map data needs to be loaded, and then acting accordingly.
Any ideas on how to accomplish this, or how to otherwise (reliably) select an annotation after re-centering the map view on the coordinate where that annotation lives?
Clues appreciated - thanks!
I ran into the same problem, but found what seems like a reliable and reasonable solution:
Implement the delegate method mapView:didAddAnnotationViews:. When I tried selecting the annotation directly within the delegate method, the callout dropped with the pin! That looked odd, so I add a slight delay of a half-second.
-(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
[self performSelector:#selector(selectInitialAnnotation)
withObject:nil afterDelay:0.5];
}
Select the initial annotation as you'd expect, but calling selectAnnotation:animated;
-(void)selectInitialAnnotation {
[self.mapView selectAnnotation:self.initialAnnotation animated:YES];
}
It seems that selectAnnotation:animated: is not called under some conditions. Compare with MKMapView docs:
If the specified annotation is not onscreen, and therefore does not
have an associated annotation view, this method has no effect.
A more consistent way than using a fixed timeout is to listen to the regionDidChange callback. Set the center coordinate of the map to the desired annotation, and when the regionDidChange method is called, then select the annotation in the center, to open the callout.
Here's a little video I took of the thing running randomly between 5 pins.
First, goto the center coordinate of the annotation. Let's say the annotation object is named thePin.
- (void)someMethod {
[map setCenterCoordinate:thePin.coordinate animated:YES];
}
Then in the regionDidChange method, select this annotation.
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
[map selectAnnotation:thePin animated:YES];
}
Well just FYI, this is what the docs say,
If the specified annotation is not onscreen, and therefore does not
have an associated annotation view, this method has no effect.
So if I want to call setCenterCoordinate:animated: or setRegion:animated: and then I want to select the annotation by calling, selectAnnotation:animated: , the annotation won't get selected and the callout won't appear beacause of the exact same reason mentioned above in docs, So the way it would be great to have something like, setCenterCoordinate:animated:ComletionBlock but its not there..! The way that worked for me is as below,
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut animations:^{
[self.mapView setCenterCoordinate:location.coordinate animated:YES];
} completion:^(BOOL finished) {
[self.mapView selectAnnotation:location animated:YES];
}];
This will give you a completion block and u can use that to select the annotation.
What has worked for me was calling selectAnnotation:animated: from the mapView:didAddAnnotationViews: method:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views;
{
[mapView selectAnnotation:[[mapView annotations] lastObject] animated:YES];
}
Apple documentation on the same here.
Note that I only had one annotation on the map so [[mapView annotations] lastObject] was fine for my purposes. Your mileage may vary.
I was having a similar problem. I was using the default MKPinAnnotationView with animatesDrop:YES. After I added the annotations to the MKMapView, I was doing this:
[mapView selectAnnotation:[mapView.annotations objectAtIndex:1] animated:YES]
which in the logic of my program, should select the nearest annotation. This wasn't working. I figured out the reason: the annotation view was not on the screen at the time of this select call, because of the pin drop animation. So all I did was set a timer to select the annotation a second later. It's a hack, but it works. I'm not sure if it'll work in every situation though, for instance on a 3G vs. 3Gs. It'd be better to figure out the right callback function to put it in.
selectTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:#selector(selectClosestAnnotation) userInfo:nil
repeats:NO];
- (void)selectClosestAnnotation {
[mapView selectAnnotation:[mapView.annotations objectAtIndex:1]
animated:YES];
}
I've found several problems with all the solutions I saw for my problem (I want to select a annotation when I added it from viewDidAppear):
Using the mapView:didAddAnnotationViews: delegate method.
This didn't work for me because if the specified annotation is not onscreen, and therefore does not have an associated annotation view, this method has no effect.
Using the mapViewDidFinishLoadingMap: delegate method.
This didn't work for me either because the map is cached now, so the method is called only once, the first time.
Solution:
John Blackburn was very close but without the mapView:didAddAnnotationViews: method. I just call my own selectAnnotation: method, before I add the annotation, with a delay:
[self.mapView addAnnotation:ann];
[self performSelector:#selector(selectAnnotation:) withObject:ann afterDelay:0.5];
And this is what I do in my selectAnnotation: method:
- (void)selectAnnotation:(id < MKAnnotation >)annotation
{
[self.mapView selectAnnotation:annotation animated:YES];
}
Tested on two iPhone 3GS (iOS 5 and 6) and an iPhone 5.
I was having similar difficulty, but was actually not able to make this method work at all, let alone inconsistently.
Here is what I found to work. Maybe it will work for you too:
How to trigger MKAnnotationView's callout view without touching the pin?

Resources