MKMapView с несколькими оверлеями — проблема с памятью

Кажется, есть «проблема» с оверлеями и файлом MapKit. В отличие от аннотаций, наложения не используются повторно, поэтому добавление нескольких наложений может вызвать проблемы с памятью на реальном устройстве. У меня была эта проблема несколько раз. Итак, мой вопрос: как я могу повторно использовать MKOverlay и таким образом улучшить производительность оверлеев на MapKit?


person wkberg    schedule 16.07.2013    source источник


Ответы (2)


Ответ на этот вопрос заключается не в «повторном использовании», а в том, чтобы нарисовать их все в одном MKOverlayView, а затем нарисовать это на карте.

Несколько MKPolygons, MKOverlays и т. д. вызывают интенсивное использование памяти при рисовании на карте. Это связано с тем, что MapKit НЕ использует повторно оверлеи. Поскольку у аннотаций есть reuseWithIdentifier, у наложений нет. Каждое наложение создает новый слой в виде MKOverlayView на карте с наложением в нем. Таким образом, использование памяти будет расти довольно быстро, а использование карты станет... скажем, вялым или почти невозможным.

Поэтому есть обходной путь: вместо построения каждого наложения по отдельности вы можете добавить все MKOverlays к одному MKOverlayView. Таким образом, вы фактически создаете только один MKOverlayView и, таким образом, нет необходимости повторно использовать его.

Это обходной путь, в данном случае для MKPolygons, но он не должен сильно отличаться для других, таких как MKCircles и т. д.

создать класс: MultiPolygon (подкласс 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

Теперь создайте класс: MultiPolygonView (подкласс 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

Нам это импортирует MultiPolygon.h и MultiPolygonView.h в ваш ViewController

Создайте один полигон из всех: В качестве примера у меня есть массив с полигонами: polygonsInArray.

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

Добавьте allPolygonsInOne в mapView:

[mapView addOverlay:allPolygonsInOne];

Также измените метод viewForOverlay:

-(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;
 }

}

И это значительно уменьшило использование памяти для нескольких оверлеев на mapView. Вы не используете повторно сейчас, потому что нарисовано только одно OverlayView. Так что нет необходимости повторно использовать.

person wkberg    schedule 16.07.2013
comment
Я все еще не могу адаптировать это к примеру MKCircle:/ - person Tiago Almeida; 23.07.2013
comment
Какое значение я должен дать _boundinMapRect, если я адаптирую это для MKPolyline? - person Van Du Tran; 23.04.2014
comment
Это вовсе не плохая идея. Можно ли также установить цвета для каждого многоугольника/окружности???? - person MQoder; 12.05.2014
comment
я предложу - github it. - person Blind Ninja; 21.08.2015
comment
@MQoder перед визуализацией каждого многоугольника в методе - (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context в классе MultiPolygonView установите цвет в соответствии с контекстом для каждого многоугольника. Конечно, теперь вы должны наследовать MKOverlayRenderer вместо MKOverlayView, потому что MKOverlayView устарело. - person HongchaoZhang; 29.10.2019

Версия кода Objective-C для Swift 4, опубликованная @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()
        }
    }
}

Использование — добавление оверлея к вашему MKMapView:

// Add the overlay to mapView
let polygonsArray: [MKPolygon] = self.buildMKPolygons()
let multiPolygons = MultiPolygon.init(polygons: polygonsArray)
self.mapView.addOverlay(multiPolygons)

Использование — реализация viewForOverlay в вашем 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()
}
person marcelosalloum    schedule 10.03.2019
comment
Теперь это часть Apple Mapkit — developer.apple.com/documentation/mapkit/mkmultipolyline - person Abin Baby; 20.07.2021