MKMapView with multiple overlays memory-issue

ios, memory, mkmapview, mkoverlay, mkpolygon

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.

Problem

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`?

Original source