MKMapView loading all annotation views at once (including those that are outside the current rect)
Asked Answered
P

2

13

UPDATE

It looks like this problem has been quietly fixed in iOS 4.3. Up to this point, the distance that was considered "far enough" for an annotation to be recycled seemed to be hundreds of miles, even when zoomed in very closely. When I build my app with the iOS 4.3 SDK, annotations are recycled based on more reasonable limits.


Has anyone else run into this problem? Here's the code:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(WWMapAnnotation *)annotation {



// Only return an Annotation view for the placemarks. Ignore for the current location--the iPhone SDK will place a blue ball there.

NSLog(@"Request for annotation view");

if ([annotation isKindOfClass:[WWMapAnnotation class]]){



    MKPinAnnotationView *browse_map_annot_view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"BrowseMapAnnot"];



    if (!browse_map_annot_view) {
        browse_map_annot_view = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"BrowseMapAnnot"] autorelease];
        NSLog(@"Creating new annotation view");
    } else {
        NSLog(@"Recycling annotation view");
        browse_map_annot_view.annotation = annotation;
    }

...

As soon as the view is displayed, I get

2009-08-05 13:12:03.332 xxx[24308:20b] Request for annotation view
2009-08-05 13:12:03.333 xxx[24308:20b] Creating new annotation view
2009-08-05 13:12:03.333 xxx[24308:20b] Request for annotation view
2009-08-05 13:12:03.333 xxx[24308:20b] Creating new annotation view

and on and on, for every annotation (~60) I've added. The map (correctly) only displays the two annotations in the current rect. I am setting the region in viewDidLoad:

if (center_point.latitude == 0) {
    center_point.latitude = 35.785098;
    center_point.longitude = -78.669899;
}

if (map_span.latitudeDelta == 0) {
    map_span.latitudeDelta = .001;
    map_span.longitudeDelta = .001;
}

map_region.center = center_point;
map_region.span = map_span;

NSLog(@"Setting initial map center and region");

[browse_map_view setRegion:map_region animated:NO];

The log entry for the region being set is printed to the console before any annotation views are requested.

The problem here is that since all of the annotations are being requested at once, [mapView dequeueReusableAnnotationViewWithIdentifier] does nothing, since there are unique MKAnnotationViews for every annotation on the map. This is leading to memory problems for me.

One possible issue is that these annotations are clustered in a pretty small space (~1 mile radius). Although the map is zoomed in pretty tight in viewDidLoad (latitude and longitude delta .001), it still loads all of the annotation views at once.

Thanks...

Prevail answered 11/8, 2009 at 20:30 Comment(0)
F
14

What you would expect is some kind of "clipping" of the annotation views based on the current region the map displays.

This is NOT the way the dequeueReusableAnnotationViewWithIdentifier selector works.

From its documentation : As annotation views move offscreen, the map view moves them to an internally managed reuse queue. As new annotations move onscreen, and your code is prompted to provide a corresponding annotation view, you should always attempt to dequeue an existing view before creating a new one.

So the reusable mechanism only makes sense when you invoke a sequence like :

//this will create 1000 annotation views
[theMap addAnnotations:my1000annotations];
//this will move them offscreen (but some annotation views may be kept internally for further reuse)
[theMap removeAnnotatios:theMap.annotations];
//when adding back again some annotations onscreen, some of the previous annotation views will be reused.
[theMap addAnnotations:someNew400annotations];

In you case, the way I would implement the clipping (to display only the annotation for the current displayed region) is :

  • Add a delegate to your mapView and implement the - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated method to get informed when the region has changed
  • iterate through all your objects to get the ones that match this region
  • addAnnotations to your map for only those objects (you can implement a kind of merge between the previously displayed annotations and the new one or simply restart from scratch , remove all annotations, and set the new ones

Of course, when the user zooms out quite a lot and that the scope of the region is too big (too many pins to be displayed), then you have to take a decision : do I displayed all the annotations views (and take the risk that the display on the map does not give much info) or do I set a message to the user saying "zoom in to get pins" or whatever. But this is another story... ;)

Farnham answered 29/3, 2010 at 9:5 Comment(4)
This is a great answer. I had read the documentation, but I guess I was thrown by the dequeue design that sounded consistent with table cell dequeuing. With table cells, it seems like they limit the size of the queue and then start releasing the table cells (I haven't checked with the debugger, though). It's weird that the dequeing in the map isn't consistent with this. I wonder why, when the logic of maintaining a list of markers that are inside and outside of the current region already exists in the class, the developer should have to replicate this to get any actual memory savings. Thanks!!Prevail
Hum, yes, you're right, it's strange because the name is really like the one for the UITableView, and as you said, the word "offscreen" let the user think that it should work like the UITableView. And you're right as well, I think that if you're creating a table view with section size 10000, you never get 10000 UITableViewCellView instantiated because most of them are reused. further idea : when do you set the annotations in your code ? Are you 100% sure it's invoked after setting the region value ?Farnham
I'm not 100% sure, but I won't have a chance to mess with my code again until Thursday. I will take another look at it then, with your advice in mind, and let you know what happens.Prevail
If you have thousands of points, won't iterating over the collection of the annotations be slow?Fetterlock
H
0

Not sure if this will help, but you mentioned memory issues due to load of ~60 objects. Is there a way to conditionally load each object based on the current map region center and current map region span?

// :)

Hydnocarpate answered 12/10, 2009 at 15:34 Comment(1)
Well, ideally that's what dequeueReusableAnnotationViewWithIdentifier should do, I thought. I guess I could do it from scratch and manage an array of locations and every time the user zooms or pans, get the new map region, delete the appropriate regions and then add the new ones.Prevail

© 2022 - 2024 — McMap. All rights reserved.