Optimizing Code for MKMapView - Large Number of An

2019-02-01 18:50发布

问题:

I have a modal view in my app which displays a UIMapView. I then add a large number of annotations (over 800) to this map view (code below).

The problem is that the user is forced to wait a minute or so while all the pins load. Also the app becomes sluggish once all 800 pins are on the map.

Can anyone suggest how I can improve my code below ?

Thank you.

#import "MapView.h"
#import "MapPlaceObject.h"


@implementation MapView
@synthesize mapViewLink, mapLocations, detail, failedLoad;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
    // Custom initialization
}
return self;
}

-(void)addPins
{

for (MapPlaceObject * info in mapLocations) {


    double latitude = info.longitude;
    double longitude = info.latitude;

    NSString * name = info.name;
    NSString * addressline = info.addressOne;
    NSString * postcode = info.postCode;

    NSString * addresscomma = [addressline stringByAppendingString:@", "];
    NSString * address = [addresscomma stringByAppendingString:postcode];

    CLLocationCoordinate2D coordinate;
    coordinate.latitude = latitude;
    coordinate.longitude = longitude;
    MyLocation *annotation = [[[MyLocation alloc] initWithName:name address:address coordinate:coordinate] autorelease];


    [mapViewLink addAnnotation:annotation];

}
}

- (void)showLinks : (id)sender {


if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    detail = [[DetailViewController alloc] initWithNibName:@"DetailViewController-iPad" bundle:nil];
} 

else if (!detail) {

    NSLog(@"Detail is None");

    detail = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil]; 

}

int uniqueID = ((UIButton *)sender).tag;

//PlaceObject *info = [mapLocations objectAtIndex:uniqueID];

detail.UniqueID = uniqueID;
detail.hidesBottomBarWhenPushed = YES;

[self.navigationController pushViewController:detail animated:YES];

self.detail = nil;

[detail release];

}

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{

if (annotation == mapView.userLocation){
    return nil; //default to blue dot
}    

MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"currentloc"];
annView.pinColor = MKPinAnnotationColorRed;

nameSaved = annotation.title;

for (PlaceObject * info in mapLocations) {

    if (info.name == nameSaved) {

        saveID = info.UniqueID;

    }
}

UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
advertButton.frame = CGRectMake(0, 0, 23, 23);
advertButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
advertButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;

[advertButton addTarget:self action:@selector(showLinks:) forControlEvents:UIControlEventTouchUpInside];

advertButton.tag = saveID;

annView.rightCalloutAccessoryView = advertButton;

annView.animatesDrop=TRUE;
annView.canShowCallout = YES;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;

}

- (void)dealloc
{
[mapViewLink release];
[mapLocations release];
[detail release];
self.failedLoad = nil;
[failedLoad release];
[super dealloc];
}

- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];

// Release any cached data, images, etc that aren't in use.
}

- (void)viewWillAppear:(BOOL)animated {

if (firstTime) {

    CLLocationCoordinate2D zoomLocation;

    zoomLocation.latitude = 51.50801;
    zoomLocation.longitude = -0.12789;

    MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 15*METERS_PER_MILE, 15*METERS_PER_MILE);

    MKCoordinateRegion adjustedRegion = [mapViewLink regionThatFits:viewRegion];                

    [mapViewLink setRegion:adjustedRegion animated:YES];  

    firstTime = NO;

}    
}

- (void)viewDidLoad
{
[super viewDidLoad];

firstTime = YES;

failedLoad = [[NSMutableArray alloc]init];

self.mapLocations = [BluePlaqueDatabase database].mapInfo;

[self addPins];
}

- (void)viewDidUnload
{
[mapViewLink release];
mapViewLink = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}

回答1:

The two biggest speed improvements you can make here are:

  • Implement annotation view re-use (right now it creates a new view every time it needs to show an annotation even if the same one comes into view again.
  • Change how the UniqueID is set. To set it, the code is currently looping through all the annotations every time it creates an annotation view (which could happen any time the map view is zoomed or scrolled--not just the initial time).

First, instead of searching for the UniqueID in the viewForAnnotation method and using a button tag to pass the annotation identifier, add UniqueID as a property to your custom annotation class MyLocation and set the property when you add the annotation itself in addPins:

annotation.uniqueID = info.UniqueID;  // <-- give id to annotation itself
[mapViewLink addAnnotation:annotation];     

You could also add uniqueID as a parameter to the initWithName method instead of assigning the property separately.


Next, to implement annotation view re-use, the viewForAnnotation method should look like this:

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{

    if (annotation == mapView.userLocation){
        return nil; //default to blue dot
    }    

    NSString *reuseId = @"StandardPin";
    MKPinAnnotationView *annView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
    if (annView == nil)
    {
        annView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId] autorelease];

        annView.pinColor = MKPinAnnotationColorRed;
        annView.animatesDrop = YES;
        annView.canShowCallout = YES;
        annView.calloutOffset = CGPointMake(-5, 5);

        UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
        advertButton.frame = CGRectMake(0, 0, 23, 23);
        advertButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
        advertButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;

        annView.rightCalloutAccessoryView = advertButton;
    }
    else
    {
        //update the annotation property if view is being re-used...
        annView.annotation = annotation;
    }

    return annView;
}


Finally, to respond to the button press and figure out which UniqueID to show the detail for, implement the calloutAccessoryControlTapped delegate method:

- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view 
            calloutAccessoryControlTapped:(UIControl *)control
{
    MyLocation *myLoc = (MyLocation *)view.annotation;

    int uniqueID = myLoc.uniqueID;

    NSLog(@"calloutAccessoryControlTapped, uid = %d", uniqueID);

    //create, init, and show the detail view controller here...
}


After all these changes, only the initial loading of the annotations will take up most of the time. If that is still a problem, one solution is to only add annotations that would be visible in the currently displayed region and add/remove annotations as the user changes the visible region.



回答2:

I completely agree with Anna. But consider that 800 AnnotationViews at the same time will result in a extreme laggy interface. So if your map should provide user interaction like scrolling or zooming you better implement some kind clustering of your annotation views.