does MKAnnotationView buffer its input queue?
There is no guarantee that the viewForAnnotation
will be called immediately after addAnnotation
or that it will be called only once.
The annotation could be added in a region that isn't currently visible or the user might pan or zoom the map which causes the annotation to come back into view. The map view will simply call it whenever or as often as it needs to.
This is by-design and simply how the delegate method approach works.
For this reason, your implementation of the delegate method should generally only use the annotation
parameter passed to the method as the basis for all the logic inside the method. It should not rely on any external variables or make broad assumptions about when it will be called.
For other answers that may explain this in more detail, see:
- Map view annotations with different pin colors
- MKMapview annotation dynamic pin image changes after zooming
- Map annotation display all the same image/pins for all points
- Setting Map Pin colour dynamically for iOS, etc
For your question specifically, I suggest the following:
Right now you're adding annotations of type
MKPointAnnotation
which don't contain the "age" information that theviewForAnnotation
method needs (I'm assuming this is what it needs).Instead of using
MKPointAnnotation
, make yourFinding
class (or whatever the type is of theself.finding
object) implement theMKAnnotation
protocol itself. You should be able to find several examples of custom annotation classes on SO.Then, instead of keeping an
annotationFlag
variable and creatingMKPointAnnotation
objects, add theFinding
objects themselves (which contain their "age") directly to the map when callingaddAnnotation
.In
viewForAnnotation
, set thepinColor
after the if-else that creates/dequeues the view and just before thereturn
. Be sure to set thepinColor
based on the age property of theannotation
object passed into the method (which will be aFinding
type object). For example:- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation
{
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *identifier = @"myAnnotation";
MKPinAnnotationView * annotationView = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (!annotationView)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.animatesDrop = YES;
annotationView.canShowCallout = NO;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeSystem];
}else {
annotationView.annotation = annotation;
}
//update the pinColor in the view whether it's a new OR dequeued view...
if ([annotation isKindOfClass:[Finding class]])
{
Finding *f = (Finding *)annotation;
if ([f.age isEqualToString:@"2"]) {
annotationView.pinColor = MKPinAnnotationColorGreen;
}
else if ([f.age isEqualToString:@"1"]) {
annotationView.pinColor = MKPinAnnotationColorPurple;
}
else {
annotationView.pinColor = MKPinAnnotationColorRed;
}
}
return annotationView;
}
MKMapview annotation dynamic pin image changes after zooming
One issue is that the viewForAnnotation
is determining the correct image to show based upon a class instance variable. Generally the identifier for the annotation's image would be a property of the custom annotation itself, not some external instance variable.
On top of that, it appeared that the annotation was being added to the map before all of the annotation's properties were being set. One should defer the addAnnotation
until all of the annotation's properties are set.
Alternatively, you can add the annotations to a NSMutableArray
, tweak them as you see fit, and only add the annotations at the very end using the addAnnotations
(note the s), passing it the array.
Map view annotations with different pin colors
The viewForAnnotation
delegate method isn't necessarily called immediately after you do addAnnotation
and it can also be called at other times by the map view when it needs to get the view for an annotation (while your code is doing something completely different).
So you can't depend on the value of an ivar being in sync with some code outside that delegate method.
Instead, add the yesno
property to your custom MapViewAnnotation
class, set it when creating the annotation and then access its value in viewForAnnotation
through the annotation
parameter (ie. the map view is giving you a reference to the exact annotation object it wants the view for).
Example:
MapViewAnnotation *newAnnotation = [[MapViewAnnotation alloc] init...
newAnnotation.yesno = tempObj.yesno; // <-- set property in annotation
[self.mapView addAnnotation:newAnnotation];
Then in viewForAnnotation
:
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation
{
if (![annotation isKindOfClass:[MapViewAnnotation class]])
{
// Return nil (default view) if annotation is
// anything but your custom class.
return nil;
}
static NSString *reuseId = @"currentloc";
MKPinAnnotationView *annView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (annView == nil)
{
annView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
annView.animatesDrop = NO;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
}
else
{
annView.annotation = annotation;
}
MapViewAnnotation *mvAnn = (MapViewAnnotation *)annotation;
if (mvAnn.yesno)
{
annView.pinColor = MKPinAnnotationColorGreen;
}
else
{
annView.pinColor = MKPinAnnotationColorRed;
}
return annView;
}
viewForAnnotation confusion and customizing the pinColor iteratively
First, the code should not be calling viewForAnnotation
explicitly itself.
Remove the explicit call to viewForAnnotation
after the addAnnotation
line.
viewForAnnotation
is a delegate method and the map view will call it automatically when it needs to display an annotation. If it's not getting called automatically, make sure the map view's delegate
property is set (to self
for example).
Second (and the real issue), is that the code assumes that the viewForAnnotation
delegate method will get called only once and immediately after adding each annotation.
This is not the case and is not guaranteed. The map view will call viewForAnnotation
whenever it needs to display the annotation and could be called multiple times for the same annotation or long after the annotation is actually added (eg. after user pans or zooms the map and the annotation comes into view).
See does MKAnnotationView buffer its input queue? for some additional details and relevant links to other answers including sample code.
Basically, you must store the properties that affect an annotation's view with the annotation object itself and retrieve these properties from the annotation
parameter that is passed into viewForAnnotation
.
What I suggest for your case is this:
I assume you are using the built-in annotation class MKPointAnnotation
. Instead of using MKPointAnnotation
which does not let you store your custom status
property alongwith the annotation object itself, either:
Create a custom class that implements the
MKAnnotation
protocol but also with astatus
property. Set this property when creating the annotation and extract its value from theannotation
parameter passed intoviewForAnnotation
and set thepinColor
accordingly. See sample code in linked answer(s).Make the objects in the
MySupplierData
themselves objects that implement theMKAnnotation
protocol. So if the objects inMySupplierData
are instances of some class named, say,Supplier
, make theSupplier
class conform to theMKAnnotation
protocol and then you can add theMySupplierData
objects themselves to the map view when callingaddAnnotation
.
Map annotation display all the same image/pins for all points
If, as you say, "this did work in iOS 6", you should consider it quite fortunate that it did (or seemed to) and this approach of setting the annotation's image should not be relied on under any version.
Although @Ar Ma is correct that the annotation view's annotation
property should be set (in case the view is being re-used), that won't solve the main issue.
The annotation view's image
is set based on the value of categoryIdNumber
which seems to be some variable outside the viewForAnnotation
delegate method.
You cannot assume that:
viewForAnnotation
will be called immediately after you calladdAnnotation
. Even in iOS 6 or earlier, this is not guaranteed.viewForAnnotation
will be called only once for each annotation. The delegate method can be called multiple times for the same annotation as the user pans or zooms the map and the annotation comes back onto the screen.viewForAnnotation
will be called in the same order that you add the annotations. This is a result of points 1 and 2.
I assume that just before you call addAnnotation
, the categoryIdNumber
is set correctly and then based on the above incorrect assumptions, viewForAnnotation
uses categoryIdNumber
to set the image.
What is happening is that viewForAnnotation
is being called by the map view sometime after all or some of the addAnnotation
calls are done at which point categoryIdNumber
is probably the value connected with the last annotation added and all the annotations use the image applicable to the last annotation.
To fix this (regardless of the iOS version), you must put the correct categoryIdNumber
value into each annotation object before calling addAnnotation
.
It looks like your annotation class is MyAnnotation
and you already have a catMapId
property in it.
You must set this property in the annotation before calling addAnnotation
-- not inside the viewForAnnotation
method which is too late. (By the way, you are creating a MyAnnotation
object inside the viewForAnnotation
method which is pointless.)
So where you create and add the annotations (not in viewForAnnotation
):
MyAnnotation* myAnn = [[MyAnnotation alloc] init];
myAnn.coordinate = ...
myAnn.title = ...
myAnn.catMapId = categoryIdNumber; // <-- set catMapId BEFORE addAnnotation
[mapView addAnnotation:myAnn];
Then the code in viewForAnnotation
should be like this:
- (MKAnnotationView *) mapView:(MKMapView *)mapingView viewForAnnotation:(id <MKAnnotation>) annotation
{
annView = nil;
if(annotation != mapingView.userLocation)
{
static NSString *defaultPinID = @"MyAnnId";
annView = (MKAnnotationView *)[mapingView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if ( annView == nil )
{
annView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID] ;
annView.canShowCallout = YES;
}
else
{
//view is being re-used, re-set annotation to current...
annView.annotation = annotation;
}
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
annView.rightCalloutAccessoryView = rightButton;
//Make sure we have a MyAnnotation-type annotation
if ([annotation isKindOfClass:[MyAnnotation class]])
{
//Do not CREATE a local MyAnnotation object here.
//Instead, get the catMapId from the annotation object
//that was PASSED INTO the delegate method.
//MyAnnotation* annotation= [MyAnnotation new];
//annotation.catMapId = categoryIdNumber;
MyAnnotation *myAnn = (MyAnnotation *)annotation;
//The value of the external variable categoryIdNumber is irrelevant here.
//NSLog(@"categoryIdNumber %@",categoryIdNumber);
NSLog(@"myAnn.catMapId %@",myAnn.catMapId);
//Put the NSNumber value into an int to simplify the code below.
int myAnnCatMapId = [myAnn.catMapId intValue];
NSString *imageName = nil;
switch (myAnnCatMapId)
{
case 9:
{
imageName = @"PIN_comprare.png";
break;
}
case 10:
{
imageName = @"PIN_mangiare.png";
break;
}
case 11:
{
imageName = @"PIN_mangiare.png";
break;
}
case 12:
{
imageName = @"PIN_vivere.png";
break;
}
default:
{
//set some default image for unknown cat ids...
imageName = @"default.png";
break;
}
}
annView.image = [UIImage imageNamed:imageName];
NSLog(@"annview %d", myAnnCatMapId);
}
}
return annView;
}
Changing the color of the map pin in different locations
Here's an example of what I am suggesting...
First, add a pin color property to your custom annotation class PinPlaceMark
so you can set the color of each pin individually. This property should be in the class that implements MKAnnotation
(the model object). The property doesn't have to be "pin color" -- just some value specific to each annotation that will make it easy to decide what pin color to set the view to in viewForAnnotation
:
@property (nonatomic, assign) MKPinAnnotationColor myPinColor;
Next, update your addPinWithCoordinate:
method (because that's where you are creating instances of PinPlaceMark
) to accept a pin color and set it:
- (void) addPinWithCoordinate:(CLLocationCoordinate2D)pinLocation
color:(MKPinAnnotationColor)pinColor //<-- new
{
PinPlaceMark *placeMark = [[PinPlaceMark alloc] initWithCoordinate:pinLocation];
placeMark.myTitle = @"This is my title";
placeMark.mySubTitle = @"This is my subtitle";
placeMark.myPinColor = pinColor; //<-- new
[self.mapView addAnnotation:placeMark];
}
Next, update the code that calls addPinWithCoordinate:
. Example also shows multiple pins being added with different colors:
CLLocationCoordinate2D pinLocation1;
pinLocation1.latitude = 40.7101843;
pinLocation1.longitude = -74.0061474;
[self addPinWithCoordinate:pinLocation1 color:MKPinAnnotationColorGreen];
CLLocationCoordinate2D pinLocation2 = CLLocationCoordinate2DMake(41, -75);
[self addPinWithCoordinate:pinLocation2 color:MKPinAnnotationColorRed];
CLLocationCoordinate2D pinLocation3 = CLLocationCoordinate2DMake(32, -80);
[self addPinWithCoordinate:pinLocation3 color:MKPinAnnotationColorPurple];
CLLocationCoordinate2D pinLocation4 = CLLocationCoordinate2DMake(50, -90);
[self addPinWithCoordinate:pinLocation4 color:MKPinAnnotationColorGreen];
CLLocationCoordinate2D pinLocation5 = CLLocationCoordinate2DMake(40, -120);
[self addPinWithCoordinate:pinLocation5 color:MKPinAnnotationColorRed];
CLLocationCoordinate2D pinLocation6 = CLLocationCoordinate2DMake(45, -100);
[self addPinWithCoordinate:pinLocation6 color:MKPinAnnotationColorPurple];
Finally, update the viewForAnnotation
delegate method to check if the annotation is of type PinPlaceMark
and use its myPinColor
property:
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if (! [annotation isKindOfClass:[PinPlaceMark class]]) {
//if annotation is not a PinPlaceMark (eg. user location),
//return nil so map view draws default view (eg. blue dot) for it...
return nil;
}
static NSString *reuseId = @"id";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (pinView == nil) {
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
pinView.canShowCallout = YES;
}
else {
pinView.annotation = annotation;
}
//Set annotation-specific properties **AFTER**
//view is dequeued or created...
PinPlaceMark *ppm = (PinPlaceMark *)annotation;
pinView.pinColor = ppm.myPinColor;
return pinView;
}
Related Topics
Mkmapview Show Incorrectly Saved Region
Is Possible to Simulate Touch Event Using an External Keyboard on iOS Jailbroken
How to Hide 'Back' Button on Navigation Bar on Iphone
How to Draw a Circle in iOS Swift
Uifont - How to Get System Thin Font
How to Use the Uisearchbar and Uisearchdisplaycontroller
How to Retrieve Facebook Response Using Facebook iOS Sdk
How to Set Initial Values for Nsuserdefault Keys
Loading Viewcontroller from Xib File
Custom Cordova Plugin: Add Framework to "Embedded Binaries"
Disable Bounce Effect in Uipageviewcontroller
Presenting Uiviewcontroller from Skscene
Xcode: Failed to Get the Task for Process
How to Implement a Box or Gaussian Blur on iOS
How to Hide "-" (Delete) Button While Editing Uitableview
Canopenurl - This App Is Not Allowed to Query for Scheme Instragram
Blocks on Swift (Animatewithduration:Animations:Completion:)