MKMapView使用多个覆盖层的内存问题

8

似乎在使用地图套件(MapKit)时,叠加层存在一些“问题”。与标注不同,叠加层并不会被重复使用,因此在添加多个叠加层时可能会导致真实设备上的内存问题。我已经遇到过这个问题多次。那么我的问题是,如何重复使用 MKOverlay 以提高 MapKit 上叠加层的性能?

2个回答

17

答案不是"重复使用(reusing)",而是将它们全部绘制到一个MKOverlayView中,然后在地图上进行绘制。

当在地图上绘制多个MKPolygonsMKOverlays等时,会导致内存占用较大。这是由于MapKit未对覆盖层进行重用所导致的。虽然注释具有reuseWithIdentifier,但叠加层没有。每个叠加层都会在地图上创建一个新的图层,其中包含该叠加层的MKOverlayView。这样,内存使用量会快速上升,地图使用起来则变得... 难以忍受。

因此有一个解决方法:不要单独绘制每个叠加层,而是将所有的MKOverlays添加到一个MKOverlayView中。这样实际上只创建了一个MKOverlayView,因此无需重复使用。

这是解决办法,对于MKPolygons等其他类似的对象,也适用。

创建一个类:MultiPolygonNSObject子类)

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

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

现在创建一个类:MultiPolygonViewMKOverlayPathView的子类)

MultiPolygonView.h中:

#import <MapKit/MapKit.h>

@interface MultiPolygonView : MKOverlayPathView

@end

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

在您的ViewController中导入MultiPolygon.hMultiPolygonView.h

将所有多边形创建为一个: 作为示例,我有一个包含多边形的数组:polygonsInArray

MultiPolygon *allPolygonsInOne = [[MultiPolygon alloc] initWithPolygons:polygonsInArray];
将allPolygonsInOne添加到地图视图中:
[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。所以不需要重复使用。


我仍然无法将此适应于MKCircle示例:/ - Tiago Almeida
如果我将其适用于MKPolyline,那么_boundinMapRect应该赋什么值? - Van Du Tran
1
这真不是一个坏主意。是否也可以针对每个多边形/圆设置颜色? - MQoder
3
我会建议你去"GitHub"。 - Blind Ninja
MultiPolygonView类的方法 - (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context 中渲染每个多边形之前,为每个多边形设置上下文颜色。当然,现在应该继承MKOverlayRenderer而不是MKOverlayView,因为MKOverlayView已经被弃用了。 - HongchaoZhang

4

这是一个关于IT技术的翻译。以下是@wkberg发布的Objective-C代码的Swift 4版本:

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: https://dev59.com/OHTYa4cB1Zd3GeqPvYLo#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)

使用方法 - 在您的MKMapViewDelegate中实现viewForOverlay

// 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()
}

1
这现在是苹果Mapkit的一部分 - https://developer.apple.com/documentation/mapkit/mkmultipolyline - Abin Baby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接