iosios7mkmapviewmapkitmkmapsnapshotter

Snapshot of MKMapView in iOS7


I am trying to create a snapshot of a MKMapView in iOS7 application the same way it's recommended everywhere for previous iOS versions:

- (UIImage*) renderMapViewToImage
{
   UIGraphicsBeginImageContextWithOptions(mapView.frame.size, NO, 0.0);
   [mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
   UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext(); 
   return image;
}

However, the image returned is a black rectangle with a blue current location dot on top of it. I've tried using different sublayers of the mapView as well, but the result is always the same.

Does anyone know how to take MKMapView snapshots in iOS7 ?


Solution

  • You can use MKMapSnapshotter and grab the image from the resulting MKMapSnapshot. See the discussion of it WWDC 2013 session video, Putting Map Kit in Perspective.

    For example:

    MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
    options.region = self.mapView.region;
    options.scale = [UIScreen mainScreen].scale;
    options.size = self.mapView.frame.size;
    
    MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:options];
    [snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
        UIImage *image = snapshot.image;
        NSData *data = UIImagePNGRepresentation(image);
        [data writeToFile:[self snapshotFilename] atomically:YES];
    }];
    

    Having said that, the renderInContext solution still works for me. There are notes about only doing that in the main queue in iOS7, but it still seems to work. But MKMapSnapshotter seems like the more appropriate solution for iOS7.


    If you want to include some annotations in the snapshot, you have to draw them manually (!). This is discussed in some detail at the end of the Putting Map Kit in Perspective video. I have to say that this is one of the least elegant implementations that I've ever seen Apple advise. Anyway, in iOS, it might look like:

    MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
    options.region = self.mapView.region;
    options.scale = [UIScreen mainScreen].scale;
    options.size = self.mapView.frame.size;
    
    MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:options];
    [snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) completionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
    
        // get the image associated with the snapshot
    
        UIImage *image = snapshot.image;
    
        // Get the size of the final image
    
        CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height);
    
        // Get a standard annotation view pin. Clearly, Apple assumes that we'll only want to draw standard annotation pins!
    
        MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""];
        UIImage *pinImage = pin.image;
    
        // ok, let's start to create our final image
    
        UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
    
        // first, draw the image from the snapshotter
    
        [image drawAtPoint:CGPointMake(0, 0)];
    
        // now, let's iterate through the annotations and draw them, too
    
        for (id<MKAnnotation>annotation in self.mapView.annotations)
        {
            CGPoint point = [snapshot pointForCoordinate:annotation.coordinate];
            if (CGRectContainsPoint(finalImageRect, point)) // this is too conservative, but you get the idea
            {
                CGPoint pinCenterOffset = pin.centerOffset;
                point.x -= pin.bounds.size.width / 2.0;
                point.y -= pin.bounds.size.height / 2.0;
                point.x += pinCenterOffset.x;
                point.y += pinCenterOffset.y;
    
                [pinImage drawAtPoint:point];
            }
        }
    
        // grab the final image
    
        UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    
        // and save it
    
        NSData *data = UIImagePNGRepresentation(finalImage);
        [data writeToFile:[self snapshotFilename] atomically:YES];
    }];
    

    For MacOS implementation, see that video for more information, but the technique is basically the same (the mechanism for creating the images is slightly different).