Кажется, есть «проблема» с оверлеями и файлом MapKit
. В отличие от аннотаций, наложения не используются повторно, поэтому добавление нескольких наложений может вызвать проблемы с памятью на реальном устройстве. У меня была эта проблема несколько раз. Итак, мой вопрос: как я могу повторно использовать MKOverlay и таким образом улучшить производительность оверлеев на MapKit
?
MKMapView с несколькими оверлеями — проблема с памятью
Ответы (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
. Так что нет необходимости повторно использовать.
github it
.
- person Blind Ninja; 21.08.2015
- (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()
}