How to Take a Snapshot of a Uiview That Isn't Rendered

How can I take a snapshot of a UIView that isn't rendered?

drawViewHierarchyInRect will work for you. You can use this directly on your WKWebView.

There is some useful detail on this Apple Technical Q&A. Also I touch on it here in answer to a slightly different question.

I use it in a category on UIView:

@implementation UIView (ImageSnapshot)
- (UIImage*)imageSnapshot {
UIGraphicsBeginImageContextWithOptions(self.bounds.size,
YES, self.contentScaleFactor);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
@end

I don't know what you mean by 'the obvious method' - but I have tested this on a WKWebView and it works.

Capture a Screen Shot of UIView - Slow Performance

Did you use the "Time Profiler" Instrument ("Product" Menu -> "Profile") to check where in your code you spend the most of your time? (use it with your Device of course, not the Simulator, to have realistic profiling). I'd guess it is not in the image capture portion you quoted in your question, but in your rescaling method imageWithImage:scaledToSize: method.

Instead of rendering the image at its whole size in a context, then rescaling the image to the final size, you should render the layer in the context directly at the expected size by applying some affine transform to the context.

So simply use CGContextConcatCTM(someScalingAffineTransform); on UIGraphicsGetCurrentContext() right after your line UIGraphicsBeginImageContext(size);, to apply an scaling affine transform that will make the layer be rendered at a different scale/size.

This way it will be directly rendered as the expected size which will be much faster, instead of being rendered at 100% and then having you to rescale it afterwards in a time-consuming way

How to convert a UIView to an image

An extension on UIView should do the trick.

extension UIView {

// Using a function since `var image` might conflict with an existing variable
// (like on `UIImageView`)
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}

Apple discourages using UIGraphicsBeginImageContext starting iOS 10 with the introduction of the P3 color gamut. UIGraphicsBeginImageContext is sRGB and 32-bit only. They introduced the new UIGraphicsImageRenderer API that is fully color managed, block-based, has subclasses for PDFs and images, and automatically manages the context lifetime. Check out WWDC16 session 205 for more details (image rendering begins around the 11:50 mark)

To be sure that it works on every device, use #available with a fallback to earlier versions of iOS:

extension UIView {

// Using a function since `var image` might conflict with an existing variable
// (like on `UIImageView`)
func asImage() -> UIImage {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
}

Snapshot scaled UIView

To draw the complete content view of a scroll view you could use something like:

@IBAction func onClear(_ sender: Any) {
self.targetImageView.image = nil
}

@IBAction func onSnapshot(_ sender: Any) {
UIGraphicsBeginImageContextWithOptions(self.contentView.bounds.size, false, UIScreen.main.scale)
if let context = UIGraphicsGetCurrentContext() {
self.contentView.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
self.targetImageView.image = image
} else {
self.targetImageView.image = nil
}
UIGraphicsEndImageContext()
}

Test

In the upper area there is a scroll view with activated zoom (maximumZoomScale = 5). In the lower area there is only a simple UIImageView. If one taps on the button with the label "snap", onSnapshot is called and the lower UIImageView is filled with the snapshot image. With the button 'clear' UIImageView's image is set to nil.

snapshot demo



Related Topics



Leave a reply



Submit