6

I'm trying to capture a screenshot of a WKWebView but my method doesn't work properly, it returns a solid color as if the layer tree was empty, while it seems to work on other views.

- (UIImage *)screenshot
{
    UIImage *screenshot;

    UIGraphicsBeginImageContext(self.frame.size);

    [self.layer renderInContext:UIGraphicsGetCurrentContext()];

    screenshot = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return screenshot;
}
dandan78
  • 13,328
  • 13
  • 64
  • 78
Vulkan
  • 1,004
  • 16
  • 44
  • Have you found a solution for iOS? Currently i'm using snapshotViewAfterScreenUpdates. But I have to call it after the webview is rendered, so I wait for a 0.1 secs in didFinishNavigation and call snapshotViewAfterScreenUpdates. Not very convinient and reliably( – sidslog Dec 06 '14 at 23:44
  • Faced the same issue with video recording of WKWebView. Its just a white square, while UIWebView records correctly. Looks no decision found yet – Dren Apr 06 '15 at 12:27
  • 2
    FYI, renderInContext works on iOS 10, and in the simulator on iOS 9, but not devices with iOS 9. Be careful with your testing. – scosman Nov 25 '16 at 23:15
  • This is still bugging me in 2019 with iOS 12 - very flaky area! – Andy Dent Feb 14 '19 at 14:36

7 Answers7

4

This Stackoverflow answer solved it for me on iOS 8 with a WKWebView where [view snapshotViewAfterScreenUpdates:] and [view.layer renderInContext:] either returned solid black or white:

UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 0);
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
UIImage* uiImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Community
  • 1
  • 1
user2067021
  • 4,399
  • 37
  • 44
  • 1
    It's worth paying attention to the return value of `drawViewHierarchyInRect:afterScreenUpdates:` though. If you’re trying to render the entire contents of a WKWebView by temporarily resizing the view before calling this method, I’ve found that the method returns NO, indicating that not all the content was drawn, and this shows up as grey areas in the resultant UIImage. – user2067021 May 22 '15 at 01:13
3

There's still a bug with the WKWebView but you can work your way around it by using another function:

[webView snapshotViewAfterScreenUpdates:NO or YES];

It works.

Vulkan
  • 1,004
  • 16
  • 44
3

Please refer to this answer

iOS 11.0 and above, Apple has provided following API to capture snapshot of WKWebView.

@available(iOS 11.0, *)
    open func takeSnapshot(with snapshotConfiguration: WKSnapshotConfiguration?, completionHandler: @escaping (UIImage?, Error?) -> Swift.Void)

Sample usage:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {

        if #available(iOS 11.0, *) {
            webView.takeSnapshot(with: nil) { (image, error) in
                //Do your stuff with image
            }
        }
    }
Maverick
  • 3,209
  • 1
  • 34
  • 40
2

Here are my two points. Before the iOS 10.3.1 renderInContext used to work well for the WKWebView. But only if a WKWebView is included in a presented view hierarchy. In other words, the system is drawing it on the screen.

In the iOS 10.3.1, renderInContext doesn't work for me. In the same moment, drawViewHierarchyInRect works fine in the iOS 10.3.1. But only if it's in the view hierarchy! Even worse. When I was trying to get a snapshot of the WKWebView, which wasn't presented on the current screen, the original view became invalidated. Thus taking screenshot can break a WKWebView.

Here is my workaround.

  1. I save a snapshot (UIImage) with drawViewHierarchyInRect every time when I need it. But only if the WKWebView is on the screen.
  2. If the WKWebView isn't in the view hierarchy I use a saved snapshot.
Vladimir Vlasov
  • 1,860
  • 3
  • 25
  • 38
1

The drawViewHierarchyInRect method will work, but will cause the screen to briefly flash.

If you need high performance (capturing many frames quickly), I suggest adding the WKWebView to your window somewhere (even if you push it out of the visible area with the frame offset or make it transparent). I found that renderInContext is much faster, and works fine as long as the view is in the view hierarchy of the window.

scosman
  • 2,343
  • 18
  • 34
0

I got it working, but needed the following things:

  • WKWebView must be on the view hierarchy, for example add it to the root view controller, send it to the back, and/or hide it until you need it
  • yourWebView.frame must contain the whole content to work, I got it by doing yourWebView.frame = yourWebView.scrollView.contentSize
  • If you found yourself with some parts of the image to be cropped, there is a weird limitation to the ram of the device, try forcing the frame to a bigger or ridiculous size, like 10000 points, then try to render again.
  • the didFinish delegate method sometimes gets called before the real drawing happens and when you draw there is nothing there yet, I solved it by calling the draw routine after a short delay with perform(#selector(process(webView:)), with: webView, afterDelay: 0.01)
Juan Boero
  • 6,281
  • 1
  • 44
  • 62
-1

Try this

- (IBAction)takeScreenShot:(id)sender {

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
CGRect rect = [keyWindow bounds];
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[keyWindow.layer renderInContext:context];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

UIImageWriteToSavedPhotosAlbum(img, self,
                               @selector(image:didFinishSavingWithError:contextInfo:), nil);

}

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error
  contextInfo:(void *)contextInfo
{
// Was there an error?
if (error != NULL)
{
    // Show error message...
    UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"Image not Saved" message:@"" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
    [alertView show];
}
else  // No errors
{
    // Show message image successfully saved
    UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"Image Saved" message:@"This chart has been save to your photos" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
    [alertView show];
}
}
  • 1
    Nup, still doesn't work. I get exactly the same image in the screenshot where the WKWebView should be. Only the background colour of the webpage exists in the WKWebView's layer tree. – Vulkan Nov 10 '14 at 13:29
  • 1
    There's no error because nothing went wrong in the process. The layers are just not in the web view. The web view layers have only 1 layer. The background layer. That's it... IT'S A HUGE BUG! – Vulkan Nov 12 '14 at 17:38