正确使用MKOverlayView

发布于 2024-10-01 19:22:41 字数 1498 浏览 9 评论 0原文

我正在编写一个 iPhone 应用程序,其中使用 MKOverlayView 将大 PNG 图像 (1936 × 2967) 放置在 MKMapView 上。我对如何在 MKOverlayView 中正确实现 drawMapRect: 函数有点困惑 - 我应该在绘制图像之前手动分割图像吗?或者我应该让 MKOverlayView 的机制来处理所有这些?

我对其他帖子的印象是,在 MKOverlayView 可用之前,您需要自己为此类任务分割图像,并使用 CATiledLayer。我想也许 MKOverlayView 处理了所有肮脏的工作。

我问的真正原因是,当我使用分配工具通过 Instruments 运行我的应用程序时,我发现随着地图上自定义图像的引入,我的应用程序使用的实时字节数稳步增加。现在我没有分割我的图像,但我在 Instruments 的泄漏工具中也没有看到内存泄漏的记录。这是我的drawMapRect:函数:

- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context{
    // Load image from applicaiton bundle
    NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"map.png"];
    CGDataProviderRef provider = CGDataProviderCreateWithFilename([imageFileName UTF8String]);
    CGImageRef image = CGImageCreateWithPNGDataProvider(provider, NULL, true, kCGRenderingIntentDefault);
    CGDataProviderRelease(provider);

    MKMapRect overlayMapRect = [self.overlay boundingMapRect];
    CGRect overlayRect = [self rectForMapRect:overlayMapRect];

    // draw image
    CGContextSaveGState(context);
    CGContextDrawImage(context, overlayRect, image);
    CGContextRestoreGState(context);

    CGImageRelease(image);

}

如果我的drawMapRect:函数不是这些内存问题的原因,有人知道它可能是什么吗?通过调试我知道我的 viewForOverlay: mapView 函数只会为每个覆盖调用一次,所以并不是内存泄漏或其他原因。

欢迎任何建议!

谢谢,-Matt

编辑:所以事实证明内存问题实际上是由 MKMapView 引起的 - 每次我移动地图时,内存使用量都会非常稳定地上升并且永远不会下降 - 这看起来不太好:(

I am writing an iPhone app in which I place a large PNG image (1936 × 2967) on an MKMapView using MKOverlayView. I am a little confused about how to appropriately implement the drawMapRect: function in MKOverlayView - should I manually segment my image before drawing it? Or should I let the mechanisms of MKOverlayView handle all that?

My impression from other posts is that before MKOverlayView was available, you were expected to segment images yourself for this kind of task, and use a CATiledLayer. I thought maybe MKOverlayView took care of all the dirty work.

The real reason I ask though is because when I run my app through Instruments using the allocations tool, I find that the number of live bytes my app is using steadily increases with the introduction of the custom image on the map. Right now I am NOT segmenting my image, but I also am seeing no record of memory leaks in the leaks tool in Instruments. Here is my drawMapRect: function:

- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context{
    // Load image from applicaiton bundle
    NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"map.png"];
    CGDataProviderRef provider = CGDataProviderCreateWithFilename([imageFileName UTF8String]);
    CGImageRef image = CGImageCreateWithPNGDataProvider(provider, NULL, true, kCGRenderingIntentDefault);
    CGDataProviderRelease(provider);

    MKMapRect overlayMapRect = [self.overlay boundingMapRect];
    CGRect overlayRect = [self rectForMapRect:overlayMapRect];

    // draw image
    CGContextSaveGState(context);
    CGContextDrawImage(context, overlayRect, image);
    CGContextRestoreGState(context);

    CGImageRelease(image);

}

If my drawMapRect: function is not the cause of these memory issues, does anybody know what it might be? I know through debugging that my viewForOverlay: function for the mapView only gets called once for each overlay, so it's not that memory is leaking there or something.

Any advice is welcome!

Thanks, -Matt

EDIT: so it turns out that the memory issue is actually being caused by MKMapView - every time I move the map at all the memory usage goes up very steadily and never comes down - this doesn't seem good :(

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

爱情眠于流年 2024-10-08 19:22:41

答案有点晚了,如果将来其他人遇到同样的问题,请将其留在这里。这里的缺陷是试图渲染整个图像,而文档明确指出 -

此外,您应该避免每次调用此方法时都绘制覆盖层的全部内容。相反,请始终考虑 mapRect 参数并避免在该矩形之外绘制内容。

因此,您必须只在更新的mapRect定义的区域中绘制图像的一部分

:请记住,这里的drawRect可以大于mapRect,需要相应地调整绘制和剪切区域

let overlayMapRect = overlay.boundingMapRect
let overlayDrawRect = self.rect(for: overlayMapRect)

// watch out for draw rect adjustment here --
let drawRect = self.rect(for: mapRect).intersection(overlayDrawRect)
let scaleX = CGFloat(image.width) / overlayRect.width
let scaleY = CGFloat(image.height) / overlayRect.height
let transform = CGAffineTransform.init(scaleX: scaleX, y: scaleY)
let imageCut = drawRect.applying(transform)

// omitting optionals checks, you should not
let cutImage = image.cropping(to: imageCut)

// the usual vertical flip issue with image.draw
context.translateBy(x: 0, y: drawRect.maxY + drawRect.origin.y)
context.scaleBy(x: 1, y: -1)
context.draw(image, in: drawRect, byTiling: false)

A bit of a late answer, leaving it here if somebody else hits the same problem in the future. The flaw here is trying to render a whole image while documentation clearly says -

In addition, you should avoid drawing the entire contents of the overlay each time this method is called. Instead, always take the mapRect parameter into consideration and avoid drawing content outside that rectangle.

so, you have to only draw the part of the image in the area defined by mapRect

updated: keep in mind that drawRect here can be larger than mapRect, need to adjust the paint and cut regions accordingly

let overlayMapRect = overlay.boundingMapRect
let overlayDrawRect = self.rect(for: overlayMapRect)

// watch out for draw rect adjustment here --
let drawRect = self.rect(for: mapRect).intersection(overlayDrawRect)
let scaleX = CGFloat(image.width) / overlayRect.width
let scaleY = CGFloat(image.height) / overlayRect.height
let transform = CGAffineTransform.init(scaleX: scaleX, y: scaleY)
let imageCut = drawRect.applying(transform)

// omitting optionals checks, you should not
let cutImage = image.cropping(to: imageCut)

// the usual vertical flip issue with image.draw
context.translateBy(x: 0, y: drawRect.maxY + drawRect.origin.y)
context.scaleBy(x: 1, y: -1)
context.draw(image, in: drawRect, byTiling: false)
笑着哭最痛 2024-10-08 19:22:41

这是基于 epolyakov 答案的 objc 版本。它工作得很好,但只是没有任何旋转。

- (void) drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
{
    CGImageRef overlayImage = <your_uiimage>.CGImage;

    CGRect overlayRect = [self rectForMapRect:[self.overlay boundingMapRect]];
    CGRect drawRect = [self rectForMapRect:mapRect];

    CGRect rectPortion = CGRectIntersection(overlayRect, drawRect);
    CGFloat scaleX = rotatedImage.size.width / overlayRect.size.width;
    CGFloat scaleY = rotatedImage.size.height / overlayRect.size.height;
    CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY);
    CGRect imagePortion = CGRectApplyAffineTransform(rectPortion, transform);

    CGImageRef cutImage = CGImageCreateWithImageInRect(overlayImage, imagePortion);

    CGRect finalRect = rectPortion;

    CGContextTranslateCTM(context, 0, finalRect.origin.y + CGRectGetMaxY(finalRect));
    CGContextScaleCTM(context, 1.0, -1.0);

    CGContextSetAlpha(context, self.alpha);
    CGContextDrawImage(context, finalRect, cutImage);
}

如果您还需要管理图像的旋转,我发现了一个使用原始图像的旋转版本的技巧(这是因为地图渲染总是绘制垂直矩形,并且在此方法中旋转图像会剪切它)。
因此,使用原始图像的旋转版本可以按照地图的预期使用垂直矩形进行渲染

    UIImage* rotatedImage = [self rotatedImage:<your_uiimage> withAngle:<angle_of_image>];

    CGImageRef overlayImage = rotatedImage.CGImage;

,这就是在边界矩形中生成旋转图像的方法

- (UIImage*) rotatedImage:(UIImage*)image withAngle:(CGFloat)angle
{
    float radians = degreesToRadians(angle);

    CGAffineTransform xfrm = CGAffineTransformMakeRotation(radians);
    CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
    CGRect rotatedImageBoundingRect = CGRectApplyAffineTransform (imageRect, xfrm);


    UIGraphicsBeginImageContext(rotatedImageBoundingRect.size);
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM (ctx, rotatedImageBoundingRect.size.width/2., rotatedImageBoundingRect.size.height/2.);
    CGContextScaleCTM(ctx, 1.0, -1.0);
    CGContextRotateCTM (ctx, radians);
    CGContextDrawImage (ctx, CGRectMake(-image.size.width / 2, -image.size.height / 2, image.size.width, image.size.height), image.CGImage);

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}

Here is the objc version based on epolyakov's answer. It works great, but only without any rotation.

- (void) drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
{
    CGImageRef overlayImage = <your_uiimage>.CGImage;

    CGRect overlayRect = [self rectForMapRect:[self.overlay boundingMapRect]];
    CGRect drawRect = [self rectForMapRect:mapRect];

    CGRect rectPortion = CGRectIntersection(overlayRect, drawRect);
    CGFloat scaleX = rotatedImage.size.width / overlayRect.size.width;
    CGFloat scaleY = rotatedImage.size.height / overlayRect.size.height;
    CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleY);
    CGRect imagePortion = CGRectApplyAffineTransform(rectPortion, transform);

    CGImageRef cutImage = CGImageCreateWithImageInRect(overlayImage, imagePortion);

    CGRect finalRect = rectPortion;

    CGContextTranslateCTM(context, 0, finalRect.origin.y + CGRectGetMaxY(finalRect));
    CGContextScaleCTM(context, 1.0, -1.0);

    CGContextSetAlpha(context, self.alpha);
    CGContextDrawImage(context, finalRect, cutImage);
}

If you need to manage also the rotation of your image, I found a trick using a rotated version of the original image (this because the map rendering always draw vertical rects and rotating the image in this method will cut it).
So using a rotated version of the original image allows to render with vertical rects as the map expects

    UIImage* rotatedImage = [self rotatedImage:<your_uiimage> withAngle:<angle_of_image>];

    CGImageRef overlayImage = rotatedImage.CGImage;

And this is the method that produce a rotated image in a bounding rect

- (UIImage*) rotatedImage:(UIImage*)image withAngle:(CGFloat)angle
{
    float radians = degreesToRadians(angle);

    CGAffineTransform xfrm = CGAffineTransformMakeRotation(radians);
    CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
    CGRect rotatedImageBoundingRect = CGRectApplyAffineTransform (imageRect, xfrm);


    UIGraphicsBeginImageContext(rotatedImageBoundingRect.size);
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM (ctx, rotatedImageBoundingRect.size.width/2., rotatedImageBoundingRect.size.height/2.);
    CGContextScaleCTM(ctx, 1.0, -1.0);
    CGContextRotateCTM (ctx, radians);
    CGContextDrawImage (ctx, CGRectMake(-image.size.width / 2, -image.size.height / 2, image.size.width, image.size.height), image.CGImage);

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文