iosmemorymkmapviewmkoverlaymkpolygon

MKMapView with multiple overlays memory-issue


There seems to be an "issue" with overlays and the MapKit. Unlike annotations, overlays aren't reused and therefore when adding multiple overlays would cause memory-problems on a real device. I've had this problem multiple times. So my question is, how can I reuse the MKOverlay and so improve the performance of overlays on MapKit?


Solution

  • The Answer to this is not "reusing" but to draw them all in to one MKOverlayView and then draw that on the map.

    Multiple MKPolygons, MKOverlays etc. cause heavy memory-usage when drawing on the map. This is due the NOT reusing of overlays by MapKit. As annotations have the reuseWithIdentifier, overlays however don't. Each overlay creates a new layer as a MKOverlayView on the map with the overlay in it. In that way memory-usage will rise quite fast and map-usage becomes... let's say sluggish to almost impossible.

    Therefore there is a work-around: Instead of plotting each overlay individually, you can add all of the MKOverlays to one MKOverlayView. This way you're in fact creating only one MKOverlayView and thus there's no need to reuse.

    This is the work-around, in this case for MKPolygons but should be not very different for others like MKCircles etc.

    create a class: MultiPolygon (subclass of NSObject)

    in MultiPolygon.h:

    #import <MapKit/MapKit.h> //Add import MapKit
    
    @interface MultiPolygon : NSObject <MKOverlay> {
     NSArray *_polygons;
     MKMapRect _boundingMapRect;
    }
    
    - (id)initWithPolygons:(NSArray *)polygons;
    @property (nonatomic, readonly) NSArray *polygons;
    
    @end
    

    in MultiPolygon.m:

    @implementation MultiPolygon
    
    @synthesize polygons = _polygons;
    
    - (id)initWithPolygons:(NSArray *)polygons
    {
     if (self = [super init]) {
        _polygons = [polygons copy];
    
        NSUInteger polyCount = [_polygons count];
         if (polyCount) {
            _boundingMapRect = [[_polygons objectAtIndex:0] boundingMapRect];
            NSUInteger i;
            for (i = 1; i < polyCount; i++) {
                _boundingMapRect = MKMapRectUnion(_boundingMapRect, [[_polygons objectAtIndex:i] boundingMapRect]);
            }
        }
     }
     return self;
    }
    
    - (MKMapRect)boundingMapRect
    {
     return _boundingMapRect;
    }
    
    - (CLLocationCoordinate2D)coordinate
    {
     return MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMidX(_boundingMapRect), MKMapRectGetMidY(_boundingMapRect)));
    }
    
    @end
    

    Now create a class: MultiPolygonView (subclass of MKOverlayPathView)

    in MultiPolygonView.h:

    #import <MapKit/MapKit.h>
    
    @interface MultiPolygonView : MKOverlayPathView
    
    @end
    

    In MultiPolygonView.m:

    #import "MultiPolygon.h"  //Add import "MultiPolygon.h"
    
    
    @implementation MultiPolygonView
    
    
    - (CGPathRef)polyPath:(MKPolygon *)polygon
    
    {
     MKMapPoint *points = [polygon points];
     NSUInteger pointCount = [polygon pointCount];
     NSUInteger i;
    
     if (pointCount < 3)
         return NULL;
    
     CGMutablePathRef path = CGPathCreateMutable();
    
     for (MKPolygon *interiorPolygon in polygon.interiorPolygons) {
         CGPathRef interiorPath = [self polyPath:interiorPolygon];
         CGPathAddPath(path, NULL, interiorPath);
         CGPathRelease(interiorPath);
     }
    
     CGPoint relativePoint = [self pointForMapPoint:points[0]];
     CGPathMoveToPoint(path, NULL, relativePoint.x, relativePoint.y);
     for (i = 1; i < pointCount; i++) {
         relativePoint = [self pointForMapPoint:points[i]];
         CGPathAddLineToPoint(path, NULL, relativePoint.x, relativePoint.y);
     }
    
     return path;
    }
    
    - (void)drawMapRect:(MKMapRect)mapRect
          zoomScale:(MKZoomScale)zoomScale
          inContext:(CGContextRef)context
    {
     MultiPolygon *multiPolygon = (MultiPolygon *)self.overlay;
     for (MKPolygon *polygon in multiPolygon.polygons) {
        CGPathRef path = [self polyPath:polygon];
         if (path) {
             [self applyFillPropertiesToContext:context atZoomScale:zoomScale];
             CGContextBeginPath(context);
             CGContextAddPath(context, path);
             CGContextDrawPath(context, kCGPathEOFill);
             [self applyStrokePropertiesToContext:context atZoomScale:zoomScale];
             CGContextBeginPath(context);
             CGContextAddPath(context, path);
             CGContextStrokePath(context);
             CGPathRelease(path);
         }
     }
    }
    
    @end
    

    To us it import MultiPolygon.h and MultiPolygonView.h in your ViewController

    Create one polygon from all: As an example I've got an array with polygons: polygonsInArray.

    MultiPolygon *allPolygonsInOne = [[MultiPolygon alloc] initWithPolygons:polygonsInArray];
    

    Add the allPolygonsInOne to the mapView:

    [mapView addOverlay:allPolygonsInOne];
    

    Also change your viewForOverlay method:

    -(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
    {
    
     if ([overlay isKindOfClass:[MultiPolygon class]]) {
         MultiPolygonView *polygonsView = [[MultiPolygonView alloc] initWithOverlay:(MultiPolygon*)overlay];
         polygonsView.fillColor = [[UIColor magentaColor] colorWithAlphaComponent:0.8];
         polygonsView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.8];
         polygonsView.lineWidth = 1;
         return polygonsView;
     }
     else {
       return nil;
     }
    
    }
    

    And this greatly reduced memory usage for multiple overlays on the mapView. You're not reusing now because only one OverlayView is drawn. So no need to reuse.