2

I'm trying to take a screenshot of webpage but the image is always blank(white).

I'm using this code to convert CALayer to Data(taken from here)

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: NSBitmapImageFileType = .PNG, properties: [String : Any] = [:]) -> Data {
    let width = Int(bounds.width * self.contentsScale)
    let height = Int(bounds.height * self.contentsScale)
    let imageRepresentation = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: width, pixelsHigh: height, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSDeviceRGBColorSpace, bytesPerRow: 0, bitsPerPixel: 0)!
    imageRepresentation.size = bounds.size

    let context = NSGraphicsContext(bitmapImageRep: imageRepresentation)!

    render(in: context.cgContext)

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

}

And then to write the data to file as .png

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) 
{
    let d = web.layer?.data() as NSData?  //web is the instance of WKWebView
    d!.write(toFile: "/Users/mac/Desktop/web.png", atomically: true)
}

But I'm getting a blank(white) png instead of what I expected

1). What am I doing wrong?

2). Is there any other possible ways to get the image representation of webpage(Using swift)?

Thank you!

ssh
  • 491
  • 1
  • 8
  • 19

2 Answers2

2

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.

SkrewEverything
  • 2,393
  • 1
  • 19
  • 50
  • Thanks! I saw about that question but I never saw that github repo but sadly I too don't know objc. – ssh Jun 19 '17 at 11:10
0

Update: Swift 5 adding to prior.

I didn't want controls in the output, so I bound a key sequence to it (local key monitor):

case [NSEvent.ModifierFlags.option, NSEvent.ModifierFlags.command]:
    guard let window = NSApp.keyWindow, let wvc = window.contentViewController as? WebViewController else {
        NSSound(named: "Sosumi")?.play()
        return true
    }
    wvc.snapshot(self)
    return true

and work within a sandbox environment. We keep a bunch of user settings in the defaults like where they want snapshots to be captured (~/Desktop), so 1st time around we ask/authenticate, cache it, and the app delegate on subsequent invocations will restore sandbox bookmark(s).

var webImageView = NSImageView.init()
var player: AVAudioPlayer?

@IBAction func snapshot(_ sender: Any) {
    webView.takeSnapshot(with: nil) {image, error in
        if let image = image {
            self.webImageView.image = image
        } else {
            print("Failed taking snapshot: \(error?.localizedDescription ?? "--")")
            self.webImageView.image = nil
        }
    }
    guard let image = webImageView.image else { return }
    guard let tiffData = image.tiffRepresentation else { NSSound(named: "Sosumi")?.play(); return }

    //  1st around authenticate and cache sandbox data if needed
    if appDelegate.isSandboxed(), appDelegate.desktopData == nil {
        let openPanel = NSOpenPanel()
        openPanel.message = "Authorize access to Desktop"
        openPanel.prompt = "Authorize"
        openPanel.canChooseFiles = false
        openPanel.canChooseDirectories = true
        openPanel.canCreateDirectories = false
        openPanel.directoryURL = appDelegate.getDesktopDirectory()
        openPanel.begin() { (result) -> Void in
            if (result == NSApplication.ModalResponse.OK) {
                let desktop = openPanel.url!
                _ = self.appDelegate.storeBookmark(url: desktop, options: self.appDelegate.rwOptions)
                self.appDelegate.desktopData = self.appDelegate.bookmarks[desktop]
                UserSettings.SnapshotsURL.value = desktop.absoluteString
            }
        }
    }

    //  Form a filename: ~/"<app's name> View Shot <timestamp>"
    let dateFMT = DateFormatter()
    dateFMT.dateFormat = "yyyy-dd-MM"
    let timeFMT = DateFormatter()
    timeFMT.dateFormat = "h.mm.ss a"
    let now = Date()

    let path = URL.init(fileURLWithPath: UserSettings.SnapshotsURL.value).appendingPathComponent(
        String(format: "%@ View Shot %@ at %@.png", appDelegate.appName, dateFMT.string(from: now), timeFMT.string(from: now)))

    let bitmapImageRep = NSBitmapImageRep(data: tiffData)

    //  With sandbox clearance to the desktop...
    do
    {
        try bitmapImageRep?.representation(using: .png, properties: [:])?.write(to: path)

        // https://developer.apple.com/library/archive/qa/qa1913/_index.html
        if let asset = NSDataAsset(name:"Grab") {

            do {
                // Use NSDataAsset's data property to access the audio file stored in Sound.
                 player = try AVAudioPlayer(data:asset.data, fileTypeHint:"caf")
                // Play the above sound file.
                player?.play()
            } catch {
                Swift.print("no sound for you")
            }
        }
    } catch let error {
        NSApp.presentError(error)
        NSSound(named: "Sosumi")?.play()
    }
}
slashlos
  • 913
  • 9
  • 17