New annotation create each time - dequeueReusableAnnotationViewWithIdentifier - ios

This is my code:
- (MKAnnotationView *)mapView:(MKMapView *)aMapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation class] == MKUserLocation.class) {
return nil;
}
static NSString *identifier=#"an";
MKAnnotationView *pinView = nil;
pinView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (pinView == nil)
{
pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
pinView.canShowCallout = YES;
NSLog(#"NEW ONE CREATED");
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
It seems that is not working and pinView is always nil because I can see the nslog for each pin created.

Two thoughts:
Don't assume that just because an annotation has scrolled off the current map view's current region, that it is automatically available for being dequeued and reused for another annotation immediately. It's quite possible that MKMapView is going to hang onto annotation views, not making them available for reuse immediately. For example, I could imagine some internal optimization that might hang on to annotation views that are near the map's current region in case that the user scrolls their map such that that previous annotation view is now visible again. MKMapView probably wants to avoid creating and recreating the same annotation view as the user scrolls the MKMapView back and forth.
In my experience, you have to scroll some distance before the old annotation views are made available for reuse.
This is extremely unlikely, but in addition to my observation above, we should note that your viewForAnnotation references some external variable, mapView, rather than using the aMapView that was passed as a parameter to the method. Most likely this is some instance variable that is pointing to the same MKMapView, and everything is fine, but if that mapView variable is nil, the dequeueReusableAnnotationViewWithIdentifier will always return nil, too. You might want to change your viewForAnnotation to use the aMapView parameter to remove this ambiguity.

Related

MKAnnotation is persisting with old information

I'm developing an App where a farmer drops custom annotations ( pins with numbers ) at points of interest in his crop, emails a report with screenshot back to his office, and moves on to his next paddock.
Part of the code that sends the email include resetting properties and zeroing arrays and ivars etc. His first field works fine, but the numbering of the POI in all the fields that follow go haywire. The data represented by the errant pins are correct, just the pins themselves are not( more on that in a moment >>**).
So I've established that there is nothing wrong with anything leading up to a pin drop and I've cleared my annotations with:
[ self.mapView removeAnnotations:self.mapView.annotations];
The Apple docs mention that one also needs to implement this method in the reset:
[ self.mapView dequeueReusableAnnotationViewWithIdentifier:#"myAnnotation"];
because even though they've been cleared from the screen they are still present in memory. However that still does not fix my quandry. If I explicitly wipe my annotations out of existence with:
self.mapView.annotations = nil;
the problem still remains. Numbers on the annotation appear random in the 2nd and 3rd .. field. By logging to an onscreen textView I can see the array holding the correct values of the POI number. Something about CLLocation or MKAnnotation is still persisting somewhere. Nothing is in the Class Reference at Apple about how to reset CLLocationManager, so I presume it is done with a simple Start and Stop. My use of those are correctly balanced, so I'm not running multiple instances.
**>> Here's the snippet that decides what pin to drop, the action happens in the middle where commented
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation(id<MKAnnotation>)annotation
{
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *identifier = #"myAnnotation";
MKAnnotationView * annotationView = (MKAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (!annotationView)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
// standard stuff up to here
// findings build up as a score of each test point.
if (contentScore == 0)
{
// a green asterisk marker if no score was achieved
annotationView.image = [UIImage imageNamed:#"markerZero.png"];
} else {
// the marker number comes from idx in SiteCount Array
str = [NSString stringWithFormat:#"marker%d.png",siteCount[idx];
annotationView.image = [UIImage imageNamed:str];
}
} else {
annotationView.annotation = annotation;
}
return annotationView;
}
Currently the workaround is the fiddly job of taking it out of memory on the home screen and relaunching before beginning the next field. I could go back to using the red and green pins provided by the system but he's been spoiled by having numbers to cross-reference the report.
So where should I be looking? Who is the culprit? I suspect MKAnnonation but my knowledge has run out
As #Anna said, you are not completely reinitialising you annotation view if a reusable view is dequeued -
You should have something like
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation(id<MKAnnotation>)annotation
{
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *identifier = #"myAnnotation";
MKAnnotationView * annotationView = (MKAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (!annotationView) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
} //Note this closing brace!
// standard stuff up to here
// findings build up as a score of each test point.
if (contentScore == 0) {
// a green asterisk marker if no score was achieved
annotationView.image = [UIImage imageNamed:#"markerZero.png"];
} else {
// the marker number comes from idx in SiteCount Array
str = [NSString stringWithFormat:#"marker%d.png",siteCount[idx]; // As per #anna's comment I am not sure how you are managing this - It would be better if the number came from the associated annotation object
annotationView.image = [UIImage imageNamed:str];
}
annotationView.annotation = annotation;
}

does MKAnnotationView buffer its input queue?

I want to display different colour pins in a UIMapView based on the relative time they represent
but it seems the mapView:viewForAnnotation: method only does it's thing independent of when its called.
In my code example I have already retrieved earlier & newer locations from a file to self.fileArray .
the array holds objects called findings that has (among others) an age property .
newest findings start life as age #"0", and each time the array is reloaded ready to take new findings
they progress in age to #"1" and #"2" respectively after which they are then dropped.
Once they take on their new age property they are sent to the mapView:viewForAnnotation: method
to be displayed according to their new status as I iterate through the fileArray
the actual question is after the jump. A lot of interesting other-answers cropped up while formulating the question but none quite applied to my case
.
.
int size = [self.fileArray count];
for (int idx=(size-1); idx>0; idx--) // process backwards
{
annotationFlag = 0; // using a global just for now
self.finding = self.fileArray[idx];
if ([self.finding.age isEqualToString:#"2"]) {
[self.fileArray removeObjectAtIndex:idx];
}
if ([self.finding.age isEqualToString:#"1"]) {
self.finding.age = #"2";
[self.fileArray replaceObjectAtIndex:idx withObject:self.finding];
annotationFlag = 2;
// tried here , only displays the newest
}
if ([self.finding.age isEqualToString:#"0"]) {
self.finding.age = #"1";
[self.fileArray replaceObjectAtIndex:idx withObject:self.finding];
annotationFlag = 1;
// tried here, still only displays the same newest
}
} // end if
//<Breakpoint with Log here>
MKPointAnnotation* annotation = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D myCoordinate;
myCoordinate.latitude =[self.finding.myLat doubleValue];
myCoordinate.longitude=[self.finding.myLong doubleValue];
annotation.coordinate = myCoordinate;
[self.mapView addAnnotation:annotation];
} // end for
.
.
the annotation methods are fairly standard, as used by most everybody:
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation: (MKUserLocation *)userLocation {
_mapView.centerCoordinate = userLocation.location.coordinate;
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation {
if([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *identifier = #"myAnnotation";
MKPinAnnotationView * annotationView = (MKPinAnnotationView*)[ self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (!annotationView)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
//<Breakpoint with Log here>
switch (annotationFlag) {
case 1:
annotationView.pinColor = MKPinAnnotationColorGreen;
break;
case 2:
annotationView.pinColor = MKPinAnnotationColorRed;
break;
default:
annotationView.pinColor = MKPinAnnotationColorPurple;
break;
}
annotationView.animatesDrop = YES;
annotationView.canShowCallout = NO;
}else {
annotationView.annotation = annotation;
}
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeSystem]; // UIButtonTypeDetailDisclosure
return annotationView;
}
also under test is my neighbours dogs curiosity. the pins should show different colours for each foray
If I NSLog annotationFlag to console at various points mapView:viewForAnnotation: seems to be
ignoring the values in annotationFlag and only using the state last set, leading me to believe it is only acting when
the for loop is entirely finished, and not following iterations.
so the question is, why isn't the [self.mapView addAnnotation:annotation] call acting immediately. Ive put it within the for loop, and there is no doubling up happening there.
LATE EDIT:
using a combination of breakpoints and log-to-consoles as shown in the listings above, and commenting out the age increase processing results in an array of 42 elements ( including the old ones ready to be discarded ) and therefore 42 pins to be dropped.
When the mapView:viewForAnnotation method is reached I then have to step through for another 42 times and on the 43rd all the pins drop at once. Watching carefully its the same colour so I can verify the last colour used doesn't draw over any earlier ones. If that clarifies the problem.
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 the viewForAnnotation method needs (I'm assuming this is what it needs).
Instead of using MKPointAnnotation, make your Finding class (or whatever the type is of the self.finding object) implement the MKAnnotation protocol itself. You should be able to find several examples of custom annotation classes on SO.
Then, instead of keeping an annotationFlag variable and creating MKPointAnnotation objects, add the Finding objects themselves (which contain their "age") directly to the map when calling addAnnotation.
In viewForAnnotation, set the pinColor after the if-else that creates/dequeues the view and just before the return. Be sure to set the pinColor based on the age property of the annotation object passed into the method (which will be a Finding 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 switches between pin color annotations

I have a bunch of pins on my map, some red and some green. The initial coloring is ok. However, when I use the map view and tap on some green ones, some reds will change to green. That probably happens when they are outside of the current view area and moved into the view area.
Ideas anyone?
Here's my code snip from :
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id < MKAnnotation >)annotation{
if(((MyAnnotation*)annotation).isGreen){
AnnotationViewID = #"MyAnnotationGreen";
}else{
AnnotationViewID = #"MyAnnotationRed";
}
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[theMapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID];
}
You should use the isGreen property of your MyAnnotation object to assign the color to your MKPinAnnotationView object. The reuse code could give you an object that doesn't have all the properties set right, especially since you are using the exact same class under different identifier. You shouldn't rely on the validity of all property values of a dequeue object from view caches (such as uitableview dequeue).
do something like:
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[theMapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID];
}
[annotationView setPinColor:(annotation.isGreen)? MKPinAnnotationColorGreen : MKPinAnnotationColorRed];

Setting multiple pins on an MKMapView, and have the Callout View show the correct information?

I have an array of objects, which have the information for each pin on my map. I am able to add those with their respective coordinates onto the map using [mapView addAnnotions:array];. But when it comes to selecting a pin, and then showing the Callout view for that specific pin(using the right pin location and the right information from my array), I get lost. I also do not entirely know how Callout views work for multiple pins. I've tried looking at Apple Sample Code, but it has not helped very much, and Googling the problem does not help either.
Simple version: How are you supposed to have many pins on a map, and differentiate them when they get selected and when getting called for a callout view?
Edit: For example, how the Maps app on the iPhone works with showing the several locations of businesses, and when you tap them the correct Name and link to said business appears.
try this code
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[MNMyLocation class]]) {
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [atmLocatorMap dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
//annotationView.image=[UIImage imageNamed:#"arrest.png"];
return annotationView;
}
return nil;
}

Is there a way to subclass the MKAnnotationView used for the MKUserLocation blue dot?

I've created a custom annotation view by subclassing MKAnnotationView. This class also creates a custom callout (info pop-up 'bubble') view which is skinned to match my app.
I also want to be able to reskin the callout bubble for the user location dot, but it seems as though the only control I have over that view is whether it is overridden completely or not, by using the following inside the mapView:viewForAnnotation: method:
if(annotation == self.mapView.userLocation)
{
return nil;
}
But what I really want to do is find out what annotation view MapKit is using for the user location blue dot, and then subclass it so I can skin its callout bubble... Or is there another way? Or just no way at all?
I am not sure this will help you, but you can use the default user location annotation view, then steal the view in mapView:didSelectAnnotationView::
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
if (view == [mapView viewForAnnotation:mapView.userLocation]) {
// do what you want to 'view'
// ...
}
// ...
}
I have used this trick to change the callout title and subtitle, and add an image using leftCalloutAccessoryView. However, I haven't tried totally replacing the callout, so I don't know if it's possible.
You can use
if ([annotation isKindOfClass:[MKUserLocation class]]) { // or if(annotation == self.mapView.userLocation)
MKAnnotationView * annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"MyLocation"];
if (annotationView == nil) {
annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"MyLocation"] autorelease];
annotationView.canShowCallout = NO;
annotationView.image = [UIImage imageNamedWithBrand:#"MyLocationPin.png"];
} else {
annotationView.annotation = annotation;
}
return annotationView;
}
I think it is not possible directly, but you can override some methods in runtime with this: http://theocacao.com/document.page/266

Resources