0

I have a Cocoa App which uses WebView to load some pages and take a screenshot.

Cocoa App Code:

class ViewController: NSViewController, WebFrameLoadDelegate {

    @IBOutlet var web: WebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        web.frame = NSRect(x: 0, y: 0, width: 1024, height: 768)
        web.preferences.isJavaScriptEnabled = true
        web.preferences.isJavaEnabled = true
        web.preferences.arePlugInsEnabled = true
        web.frameLoadDelegate = self
        web.mainFrame.load(URLRequest(url: URL(string: "https://google.com")!))
        // Do any additional setup after loading the view.
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
    func webView(_ sender: WebView!, didFinishLoadFor frame: WebFrame!) {

        let queue = DispatchQueue(label: "pic")
        let delay: DispatchTimeInterval = .seconds(2)
        queue.asyncAfter(deadline: .now() + delay, execute: {
            let data = self.web.layer?.data() // data() is a extension on CALayer
            let nd = NSData(data: data!)
            nd.write(toFile: "/Users/mac/Desktop/screenshot.png", atomically: true)
        })
    }
}

It works fine and I get a screenshot of the webpage.

Now, I thought of doing it in command-line instead of Cocoa App.

Command-line Code:

class Main1: NSObject, WebFrameLoadDelegate
{
    var web: WebView!
    override init()
    {
        super.init()
        web = WebView(frame: CGRect(x: 0, y: 0, width: 1024, height: 768))
        web.preferences.isJavaScriptEnabled = true
        web.preferences.isJavaEnabled = true
        web.preferences.arePlugInsEnabled = true
        web.frameLoadDelegate = self
        web.shouldUpdateWhileOffscreen = true
        web.mainFrame.load(URLRequest(url: URL(string: "https://google.com")!))
    }

    func webView(_ sender: WebView!, didFinishLoadFor frame: WebFrame!)
    {

        let queue = DispatchQueue(label: "pic")
        let delay: DispatchTimeInterval = .seconds(2)
        queue.asyncAfter(deadline: .now() + delay, execute: {
            let data = self.web.layer?.data()
            let nd = NSData(data: data!) // <-- The data is nil error
            nd.write(toFile: "/Users/mac/Desktop/screenshot.png", atomically: true)
        })
    }
}

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

}

let m = Main1()
RunLoop.main.run()

I'm getting fatal error: unexpectedly found nil while unwrapping an Optional value.

Why can't I use same code for command-line? What might be the reason?

How can it be solved?

Thank you!

P.S. The extension for CALayer is taken from here

ssh
  • 491
  • 1
  • 8
  • 19

1 Answers1

1

I tried your command-line code on my machine.

You got it almost.

Just add web.wantsLayer = true and you will get the screenshot instead of error :-)

Tip: It creates very large image compared to phantomJS. Just try to reduce the image size if possible.

SkrewEverything
  • 2,393
  • 1
  • 19
  • 50