taking image snapshot of CATiledLayer-backed view in UIScrollView

1.1k views Asked by At

I've got a custom map view which is made of a UIScrollView. The scroll view's subview is backed by a CATiledLayer. Everything works great here. Panning & zooming loads up new map tiles and everything performs well.

What I want to do is capture frames of video of animations to this scroll view. Essentially, I want to create a video of animated changes to the scroll view's contentOffset and zoomScale.

I know that the concept is sound as I can get the private API function UIGetScreenImage() to capture the app's screen at, say, 10fps, combine these images, and I get playback animations that are smooth and have the timing curves used by the scroll view animations.

My problem, of course, is that I can't use the private API. Going through the alternatives outlined by Apple here leaves me with pretty much one supposedly valid option: asking a CALayer to renderInContext and taking a UIGraphicsGetImageFromCurrentImageContext() from that.

This just doesn't seem to work with CATiledLayer-backed views, though. A blocky, un-zoomed image is what is captured, as if the higher-resolution tiles never load. This somewhat makes sense given that CATiledLayer draws in background threads for performance and calling renderInContext from the main thread might not catch these updates. The result is similar even if I render the tiled layer's presentationLayer as well.

Is there an Apple-sanctioned way of capturing an image of a CATiledLayer-backed view during the course of the containing scroll view's animations? Or at any point, for that matter?

3

There are 3 answers

0
incanus On BEST ANSWER

BTW, this is doable if you properly implement renderLayer:inContext: in your CATiledLayer-backed view.

1
Phil Larson On

I did a quick test, and using renderInContext: on a view wrapping the scroll view seemed to work. Have you tried that?

0
Gabriel Ortega On

This code works for me.

- (UIImage *)snapshotImageWithView:(CCTiledImageScrollView *)view
{
// Try our best to approximate the best tile set zoom scale to use
CGFloat tileScale;
if (view.zoomScale >= 0.5) {
    tileScale = 2.0;
}
else if (view.zoomScale >= 0.25) {
    tileScale = 1.0;
}
else {
    tileScale = 0.5;
}

// Calculate the context translation based on how far zoomed in or out.
CGFloat translationX = -view.contentOffset.x;
CGFloat translationY = -view.contentOffset.y;
if (view.contentSize.width < CGRectGetWidth(view.bounds)) {
    CGFloat deltaX = (CGRectGetWidth(view.bounds) - view.contentSize.width) / 2.0;
    translationX += deltaX;
}
if (view.contentSize.height < CGRectGetHeight(view.bounds)) {
    CGFloat deltaY = (CGRectGetHeight(view.bounds) - view.contentSize.height) / 2.0;
    translationY += deltaY;
}

// Pass the tileScale to the context because that will be the scale used in drawRect by your CATiledLayer backed UIView
UIGraphicsBeginImageContextWithOptions(CGSizeMake(CGRectGetWidth(view.bounds) / view.zoomScale, CGRectGetHeight(view.bounds) / view.zoomScale), NO, tileScale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, translationX / view.zoomScale, translationY / view.zoomScale);

// The zoomView is a subview of UIScrollView. The CATiledLayer backed UIView is a subview of the zoomView.
[view.zoomView.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

return image;

}

Full sample code found here: https://github.com/gortega56/CCCanvasView