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
?
MKMapView with multiple overlays memory-issue
4.6k Views Asked by wkberg At
2
There are 2 best solutions below
1

A Swift 4 version of the Objective-C code posted by @wkberg:
MultiPolygon.swift:
import MapKit
/// A concatenation of multiple polygons to allow a single overlay to be drawn in the map,
/// which will consume less resources
class MultiPolygon: NSObject, MKOverlay {
var polygons: [MKPolygon]?
var boundingMapRect: MKMapRect
init(polygons: [MKPolygon]?) {
self.polygons = polygons
self.boundingMapRect = MKMapRect.null
super.init()
guard let pols = polygons else { return }
for (index, polygon) in pols.enumerated() {
if index == 0 { self.boundingMapRect = polygon.boundingMapRect; continue }
boundingMapRect = boundingMapRect.union(polygon.boundingMapRect)
}
}
var coordinate: CLLocationCoordinate2D {
return MKMapPoint(x: boundingMapRect.midX, y: boundingMapRect.maxY).coordinate
}
}
MultiPolygonPathRenderer.swift:
import MapKit
/// A MKOverlayPathRenderer that can draw a concatenation of multiple polygons as a single polygon
/// This will consume less resources
class MultiPolygonPathRenderer: MKOverlayPathRenderer {
/**
Returns a `CGPath` equivalent to this polygon in given renderer.
- parameter polygon: MKPolygon defining coordinates that will be drawn.
- returns: Path equivalent to this polygon in given renderer.
*/
func polyPath(for polygon: MKPolygon?) -> CGPath? {
guard let polygon = polygon else { return nil }
let points = polygon.points()
if polygon.pointCount < 3 { return nil }
let pointCount = polygon.pointCount
let path = CGMutablePath()
if let interiorPolygons = polygon.interiorPolygons {
for interiorPolygon in interiorPolygons {
guard let interiorPath = polyPath(for: interiorPolygon) else { continue }
path.addPath(interiorPath, transform: .identity)
}
}
let startPoint = point(for: points[0])
path.move(to: CGPoint(x: startPoint.x, y: startPoint.y), transform: .identity)
for i in 1..<pointCount {
let nextPoint = point(for: points[i])
path.addLine(to: CGPoint(x: nextPoint.x, y: nextPoint.y), transform: .identity)
}
return path
}
/// Draws the overlay’s contents at the specified location on the map.
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
// Taken from: http://stackoverflow.com/a/17673411
guard let multiPolygon = self.overlay as? MultiPolygon else { return }
guard let polygons = multiPolygon.polygons else { return }
for polygon in polygons {
guard let path = self.polyPath(for: polygon) else { continue }
self.applyFillProperties(to: context, atZoomScale: zoomScale)
context.beginPath()
context.addPath(path)
context.drawPath(using: CGPathDrawingMode.eoFill)
self.applyStrokeProperties(to: context, atZoomScale: zoomScale)
context.beginPath()
context.addPath(path)
context.strokePath()
}
}
}
Usage - Adding the overlay to your MKMapView
:
// Add the overlay to mapView
let polygonsArray: [MKPolygon] = self.buildMKPolygons()
let multiPolygons = MultiPolygon.init(polygons: polygonsArray)
self.mapView.addOverlay(multiPolygons)
Usage - Implementing the viewForOverlay in your MKMapViewDelegate
:
// Method viewForOverlay:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MultiPolygon {
let polygonRenderer = MultiPolygonPathRenderer(overlay: overlay)
polygonRenderer.lineWidth = 0.5
polygonRenderer.strokeColor = .mainGreen
polygonRenderer.miterLimit = 2.0
polygonRenderer.fillColor = UIColor.mainGreen.withAlphaComponent(0.2)
return polygonRenderer
}
return MKOverlayRenderer()
}
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 byMapKit
. As annotations have thereuseWithIdentifier
, overlays however don't. Each overlay creates a new layer as aMKOverlayView
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 oneMKOverlayView
. This way you're in fact creating only oneMKOverlayView
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 likeMKCircles
etc.create a class:
MultiPolygon
(subclass ofNSObject
)in
MultiPolygon.h
:in
MultiPolygon.m
:Now create a class:
MultiPolygonView
(subclass ofMKOverlayPathView
)in
MultiPolygonView.h
:In
MultiPolygonView.m
:To us it import
MultiPolygon.h
andMultiPolygonView.h
in your ViewControllerCreate one polygon from all: As an example I've got an array with polygons:
polygonsInArray
.Add the allPolygonsInOne to the mapView:
Also change your
viewForOverlay
method:And this greatly reduced memory usage for multiple overlays on the
mapView
. You're not reusing now because only oneOverlayView
is drawn. So no need to reuse.