13

I thought iOS 7's MKMapSnapshotters would be a simple way to take a snapshot of an MKMapView, the benefit is that you can do it without loading the map into view. Even though it seems like more work to add pins and overlays (because of the need for core graphics). The WWDC videos give a very good example of creating an MKMapSnapshotter with adding an MKAnnotationView.

However, for someone with not a lot of core graphics experience it's not really obvious how you create an MKMapSnapshotter from an MKPolylineRenderer.

I have tried to do this, but the path is inaccurate. It draws about 10% of the line accurately, but then draws the rest of the path straight.

Here's my code:

MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:snapshotOptions];

[snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
          completionHandler:^(MKMapSnapshot *snapshot, NSError *error)
{
            if (error == nil)
            {
                UIImage *mapSnapshot = [snapshot image];

                UIGraphicsBeginImageContextWithOptions(mapSnapshot.size,
                                                       YES,
                                                       mapSnapshot.scale);

                [mapSnapshot drawAtPoint:CGPointMake(0.0f, 0.0f)];

                CGContextRef context = UIGraphicsGetCurrentContext();

                //Draw the points from the MKPolylineRenderer in core graphics for mapsnapshotter...
                MKPolylineRenderer *overlay = (MKPolylineRenderer *)[self.mapView rendererForOverlay:[_mapView.overlays lastObject]];

                if (overlay.path)
                {
                    CGFloat zoomScale = 3.0;

                    [overlay applyStrokePropertiesToContext:context
                                                atZoomScale:zoomScale];

                    CGContextAddPath(context, overlay.path);
                    CGContextSetLineJoin(context, kCGLineJoinRound);
                    CGContextSetLineCap(context, kCGLineCapRound);
                    CGContextStrokePath(context);
                }

                UIImage *pathImage = UIGraphicsGetImageFromCurrentImageContext();

                [map addMapIcon:pathImage];
                UIGraphicsEndImageContext();
            }
}];

Does anyone have a good workable example on how to do this please?

n00bProgrammer
  • 4,261
  • 3
  • 32
  • 60
PostCodeism
  • 1,070
  • 1
  • 12
  • 20

1 Answers1

13

Just had the same problem, this code seems to work:

UIImage * res = nil;
UIImage * image = snapshot.image;

UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
[image drawAtPoint:CGPointMake(0, 0)];

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetStrokeColorWithColor(context,  [COLOR_FLASHBLUE CGColor]);
CGContextSetLineWidth(context,2.0f);
CGContextBeginPath(context);

CLLocationCoordinate2D coordinates[[polyline pointCount]];
[polyline getCoordinates:coordinates range:NSMakeRange(0, [polyline pointCount])];

for(int i=0;i<[polyline pointCount];i++)
{
    CGPoint point = [snapshot pointForCoordinate:coordinates[i]];

    if(i==0)
    {
        CGContextMoveToPoint(context,point.x, point.y);
    }
    else{
        CGContextAddLineToPoint(context,point.x, point.y);

    }
}

CGContextStrokePath(context);

res = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
WrightsCS
  • 50,551
  • 22
  • 134
  • 186
Saa
  • 1,540
  • 10
  • 22
  • Interesting that you have to explicitly move and add the line to point rather than use CGContextAddPath. Thanks, I haven't tried it yet but will check it out :) – PostCodeism Mar 29 '14 at 17:39
  • I'm not sure that you have to do it this way. I didn't know the zoomlevel, so had to use the pointForCoordiinate method. – Saa Mar 30 '14 at 16:06
  • This worked for me too. I'd still be curious to know why CGContextAddPath doesn't work instead >_ – PostCodeism Mar 31 '14 at 22:47
  • What is the purpose of the initial `[image drawAtPoint:CGPointMake(0, 0)];` ? Also I have drawn some pin annotation as images onto the snapshot image before this and when I add the route line the pins disappear. Do I need to do them in separate contexts or something? – shim Jul 07 '17 at 21:19
  • Oh, I had an extra call to draw(at: CGPoint.zero), when I removed that extra one my pins reappeared. But what is that call for? – shim Jul 07 '17 at 21:25
  • @shim to draw the snapshotted Image into the context. You were probably over-drawing your pins with your original map? – Saa Jul 08 '17 at 05:58
  • I had an extra call to draw(at) as mentioned in the comments above. But what does that line do? – shim Jul 09 '17 at 02:10
  • @shim that line draws the map-snapshot image onto the graphical context. you do it at the start so that the rest of your context-drawing commands appear on top of the image. – danneu Oct 28 '18 at 06:50