Wkwebview Calayer to Image Exports Blank Image

WKWebView CALayer to image exports blank image

Latest Update:

Now you can take screenshot of WKWebView just like WebView.

Apple added new method for both iOS and macOS,

func takeSnapshot(with snapshotConfiguration: WKSnapshotConfiguration?, 
completionHandler: @escaping (NSImage?, Error?) -> Void)

But its still in beta.


You can't take a screenshot of WKWebView. It always returns a blank image. Even if you try to include WKWebView inside another NSView and take a screenshot, it will give you blank image in place of WKWebView.

You should use WebView instead of WKWebView for your purpose. Check this question.

If you are ok with using private frameworks(apple doesn't allow your app in its store), check this GitHub. Its written in Obj-C. I don't know Obj-C so I can't explain what's happening in that code. But it claims to do the work.

Your best approach is to use WebView and use your mentioned extension data() on the WebView.

Just a question: Why don't you use phantomJS?

PS. Sorry for the late reply. I didn't see your e-mail.

Saving WebView to PDF returns blank image?

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
}
}
}

iOS 10 and below, UIWebView has to be used to capture snapshot. Following method can be used to achieve that.

func webViewDidFinishLoad(_ webView: UIWebView) {

let image = captureScreen(webView: webView)
//Do your stuff with image
}

func captureScreen(webView: UIWebView) -> UIImage {
UIGraphicsBeginImageContext(webView.bounds.size)
webView.layer.render(in: UIGraphicsGetCurrentContext()!)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}

Here's another relevant answer

intermittent blank/partial screenshots of WKWebView using takeSnapshot

As content of WebView is bigger than the screen size so first we will get height and width of the content after that we will take a screenshot

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

// Will get Height and Width of the Content

self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
guard let contentHeight = height as? CGFloat else { print("Content height could not be obtained"); return }
self.webView.evaluateJavaScript("document.body.scrollWidth", completionHandler: { [weak self](width, error) in
let contentWidth = width as! CGFloat
let rect = CGRect(x: 0, y: 0, width: contentWidth, height: contentHeight)
self?.takeScreenshot(rect)
})
})
}
})

}

Once we will get the Height and Width, we will call below method to capture Screenshot

func takeScreenshot(_ rect: CGRect) {
webView.evaluateJavaScript(".....") { (result, error) in
let configuration = WKSnapshotConfiguration()
configuration.rect = rect//CGRect(origin: .zero, size: (self.webView?.frame.size)!)
self.webView!.takeSnapshot(with: configuration, completionHandler: { (image, error) in
// do something with the image
print("image:\(image!)")
})
}
}

Hope it will help you.

Get Image from CALayer or NSView (swift 3)

Here are some NSView options:

extension NSView {

/// Get `NSImage` representation of the view.
///
/// - Returns: `NSImage` of view

func image() -> NSImage {
let imageRepresentation = bitmapImageRepForCachingDisplay(in: bounds)!
cacheDisplay(in: bounds, to: imageRepresentation)
return NSImage(cgImage: imageRepresentation.cgImage!, size: bounds.size)
}
}

Or

extension NSView {

/// Get `Data` representation of the view.
///
/// - Parameters:
/// - fileType: The format of file. Defaults to PNG.
/// - properties: A dictionary that contains key-value pairs specifying image properties.
/// - Returns: `Data` for image.

func data(using fileType: NSBitmapImageRep.FileType = .png, properties: [NSBitmapImageRep.PropertyKey: Any] = [:]) -> Data {
let imageRepresentation = bitmapImageRepForCachingDisplay(in: bounds)!
cacheDisplay(in: bounds, to: imageRepresentation)
return imageRepresentation.representation(using: fileType, properties: properties)!
}
}

Some CALayer options:

extension CALayer {

/// Get `NSImage` representation of the layer.
///
/// - Returns: `NSImage` of the layer.

func image() -> NSImage {
let width = Int(bounds.width * contentsScale)
let height = Int(bounds.height * contentsScale)
let imageRepresentation = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: width, pixelsHigh: height, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)!
imageRepresentation.size = bounds.size

let context = NSGraphicsContext(bitmapImageRep: imageRepresentation)!

render(in: context.cgContext)

return NSImage(cgImage: imageRepresentation.cgImage!, size: bounds.size)
}
}

Or

extension CALayer {

/// Get `Data` representation of the layer.
///
/// - Parameters:
/// - fileType: The format of file. Defaults to PNG.
/// - properties: A dictionary that contains key-value pairs specifying image properties.
///
/// - Returns: `Data` for image.

func data(using fileType: NSBitmapImageRep.FileType = .png, properties: [NSBitmapImageRep.PropertyKey: Any] = [:]) -> Data {
let width = Int(bounds.width * contentsScale)
let height = Int(bounds.height * contentsScale)
let imageRepresentation = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: width, pixelsHigh: height, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)!
imageRepresentation.size = bounds.size

let context = NSGraphicsContext(bitmapImageRep: imageRepresentation)!

render(in: context.cgContext)

return imageRepresentation.representation(using: fileType, properties: properties)!
}
}

Disable magnification gesture in WKWebView

You can prevent your users from zooming by setting the delegate of your WKWebKit's UIScrollView and implementing viewForZooming(in:) as in the following:

class MyClass {
let webView = WKWebView()

init() {
super.init()
webView.scrollView.delegate = self
}

deinit() {
// Without this, it'll crash when your MyClass instance is deinit'd
webView.scrollView.delegate = nil
}
}

extension MyClass: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return nil
}
}

Screenshot without toolbar in view

Use takeSnapshot(with:completionHandler:) method to take snapshot of wkwebview. And use the completion block runs in background thread. So change the image in main thread.

class ViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
webView.load(URLRequest(url: URL(string: "https://www.facebook.com")!))

}
@IBAction func buttonAction(_ sender: UIButton) {
webView.takeSnapshot(with: nil) { (image, error) in
if let image = image {
DispatchQueue.main.async {
self.webView.isHidden = true
self.imageView.image = image
}
} else {
print(error?.localizedDescription)
}
}
}
}


Related Topics



Leave a reply



Submit