地图控件MKMapView由于要从网络上加载地图数据并在内存中缓存,因此通常占用的内存开销特别大,特别是当用户进行放大缩小、快速拖动、3d旋转时,内存基本呈直线上升,单个地图控件占用百兆内存不成问题。
假设在一个UITableView中,每个Cell的宽度和高度分别为320、150,每个Cell中都放置一个高度为320*150的MkMapView,采用Cell重用的方式,这种情况下iPhone 4s上UITableView中将最多包含4个MkMapView。又假设这里的MkMapView仅仅用于展示,不接受用户操作,此时我们保守估计每个 MkMapView占用10M内存。基于上述两种假设,不难算出,此时地图控件占用的总内存大小为40M = 4 * 10M。40M的内存对于移动应用开发,可以算是巨大的开销了。
针对这种情况,我们提出截图的方案来有效解决这个问题。举个例子,假设UITableView显示的内容如下:
第一行:北京市地图
第二行:上海市地图
第三行:广州市地图
第四行:深圳市地图
。。。。。。。。。
第N行:某某市的地图
解决方法就是:每一行的地图在头一次加载时,当地图数据加载完成后,将该地图截图保存为图片,等列表再次滚动到该行,我们用对应的图片来替代之间的地图。下面我们来说一下详细的实现步骤。
一、截图时机
通常我们在使用MKMapView地图控件时,如常用的运动的APP,会在地图上添加一些大头针(MKAnnotation)和绘制一些线条(MKOverlay)。如果你的地图控件添加了大头针以及绘制了线条,那么正确的截图时机应该满足如下三个要求:1、地图数据加载完成;2、大头针绘制完成;3、线条绘制完成。
那么问题来了,如何判断上述三种操作的已完成?经过分析,地图数据加载和大头针绘制没错,MkMapViewDelegate提供了相应的方法,分别对应的方法为:
地图数据加载完成:
大头针绘制完成:
线条绘制完成:
二、地图截图
地图控件继承于UIView,截图的方法这里就不细讲了,直接上代码:
+ (UIImage *) imageWithUIView:(UIView *)view { UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, [[UIScreen mainScreen] scale]); [view.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; }
三、保存图片
保存图片时,注意图片的命名,保证图片能够对应上相应的MKMapView,如:北京.png,上海.png,广州.png,等等。保存图片的代码如下:
+ (BOOL)saveImage:(UIImage*)image WithName:(NSString*)imageName { NSString *imageDir = [self getDir]; NSString *imagePath = [imageDir stringByAppendingPathComponent:imageName]; BOOL isCreated = [UIImagePNGRepresentation(image) writeToFile:imagePath options:NSAtomicWrite error:nil]; return isCreated; }
四、整体实现
- (void)doScreenshot { if (_isDrawAnnotationsDone && _isRenderMapDone && _isDrawOverlayDone) { UIImage *mapImage = [self imageWithUIView:_mapView]; [self saveImage:mapImage WithName:@"xx.png"]; } }
- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered { _isRenderMapDone = YES; [self doScreenshot]; }
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views { _isDrawAnnotationsDone = YES; [self doScreenshot]; }
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{ _bDrawOverlayDown = YES; [self doScreenshot]; }