WKWebView Screenshots

前端 未结 4 921
天命终不由人
天命终不由人 2021-02-08 06:59

I am trying to capture the image that the webview is displaying to the user, so I can some color analysis of the web page. When I try to get the image from it\'s parent, I am ba

相关标签:
4条回答
  • 2021-02-08 07:27

    Found myself in the same boat today but found a solution (by using private APIs).

    If you're not targeting the App Store and generally are not afraid of using private APIs, here's a way to capture screenshots of WKWebView's on OS X:

    https://github.com/lemonmojo/WKWebView-Screenshot

    0 讨论(0)
  • 2021-02-08 07:30

    You will need to have access to a target writeable place - the snapshotURL ie.., such as the desktop, so we provide a handler for that:

    func registerSnaphotsURL(_ sender: NSMenuItem, handler: @escaping (URL) -> Void) {
        var targetURL : URL
    
        //  1st around authenticate and cache sandbox data if needed
        if isSandboxed, desktopData == nil {
            targetURL =
                UserSettings.SnapshotsURL.value.count == 0
                    ? getDesktopDirectory()
                    : URL.init(fileURLWithPath: UserSettings.SnapshotsURL.value, isDirectory: true)
            
            let openPanel = NSOpenPanel()
            openPanel.message = "Authorize access to "
            openPanel.prompt = "Authorize"
            openPanel.canChooseFiles = false
            openPanel.canChooseDirectories = true
            openPanel.canCreateDirectories = true
            openPanel.directoryURL = targetURL
            openPanel.begin() { (result) -> Void in
                if (result == .OK) {
                    targetURL = openPanel.url!
                    
                    //  Since we do not have data, clear any bookmark
                    
                    if self.storeBookmark(url: targetURL, options: self.rwOptions) {
                        self.desktopData = self.bookmarks[targetURL]
                        UserSettings.SnapshotsURL.value = targetURL.absoluteString
                        if !self.saveBookmarks() {
                            print("Yoink, unable to save snapshot bookmark")
                        }
    
                        self.desktopData = self.bookmarks[targetURL]
                        handler(targetURL)
                    }
                }
                else
                {
                    return
                }
            }
        }
        else
        {
            targetURL =
                UserSettings.SnapshotsURL.value.count == 0
                    ? getDesktopDirectory()
                    : URL.init(fileURLWithPath: UserSettings.SnapshotsURL.value, isDirectory: true)
            handler(targetURL)
        }
    }
    

    we wanted to allow single (view controller) and all current views (app delegate) so two actions in their respective files, both making use of the register handler.

    App Delegate

    @objc @IBAction func snapshotAllPress(_ sender: NSMenuItem) {
        registerSnaphotsURL(sender) { (snapshotURL) in
            //  If we have a return object just call them, else notify all
            if let wvc : WebViewController = sender.representedObject as? WebViewController {
                sender.representedObject = snapshotURL
                wvc.snapshot(sender)
            }
            else
            {
                sender.representedObject = snapshotURL
                let notif = Notification(name: Notification.Name(rawValue: "SnapshotAll"), object: sender)
                NotificationCenter.default.post(notif)
            }
        }
    }
    

    View Controller

    func viewDidLoad() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(WebViewController.snapshotAll(_:)),
            name: NSNotification.Name(rawValue: "SnapshotAll"),
            object: nil)
    }
    
    @objc func snapshotAll(_ note: Notification) {
        snapshot(note.object as! NSMenuItem)
    }
    

    view singleton action

    @objc @IBAction func snapshotPress(_ sender: NSMenuItem) {
        guard let url = webView.url, url != webView.homeURL else { return }
        guard let snapshotURL = sender.representedObject as? URL else {
            //  Dispatch to app delegate to handle a singleton
            sender.representedObject = self
            appDelegate.snapshotAllPress(sender)
            return
        }
        
        sender.representedObject = snapshotURL
        snapshot(sender)
    }
    

    the webView interaction to capture an image

    @objc func snapshot(_ sender: NSMenuItem) {
        guard let url = webView.url, url != webView.homeURL else { return }
        guard var snapshotURL = sender.representedObject as? URL else { return }
        
        //  URL has only destination, so add name and extension
        let filename = String(format: "%@ Shapshot at %@",
                              (url.lastPathComponent as NSString).deletingPathExtension,
                              String.prettyStamp())
        snapshotURL.appendPathComponent(filename)
        snapshotURL = snapshotURL.appendingPathExtension("png")
        
        webView.takeSnapshot(with: nil) { image, error in
            if let image = image {
                self.webImageView.image = image
                DispatchQueue.main.async {
                    self.processSnapshotImage(image, to: snapshotURL)
                }
            }
            else
            {
                self.userAlertMessage("Failed taking snapshot", info: error?.localizedDescription)
                self.webImageView.image = nil
            }
        }
    }
    

    and the capture to the targeted area

    func processSnapshotImage(_ image: NSImage, to snapshotURL: URL) {
        guard let tiffData = image.tiffRepresentation else { NSSound(named: "Sosumi")?.play(); return }
        let bitmapImageRep = NSBitmapImageRep(data: tiffData)
    
        do
        {
            try bitmapImageRep?.representation(using: .png, properties: [:])?.write(to: snapshotURL)
            // 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.
                    let player = try AVAudioPlayer(data:asset.data, fileTypeHint:"caf")
                    // Play the above sound file.
                    player.play()
                } catch {
                    print("no sound for you")
                }
            }
            if snapshotURL.hideFileExtensionInPath(), let name = snapshotURL.lastPathComponent.removingPercentEncoding {
                print("snapshot => \(name)")
            }
        } catch let error {
            appDelegate.userAlertMessage("Snapshot failed", info: error.localizedDescription)
        }
    }
    
    0 讨论(0)
  • 2021-02-08 07:30

    Swift 3

    extension WKWebView {
        func screenshot() -> UIImage? {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0);
            self.drawHierarchy(in: self.bounds, afterScreenUpdates: true);
            let snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            return snapshotImage;
        }
    }
    

    Note: This solution only works on iOS.

    0 讨论(0)
  • 2021-02-08 07:40

    I realise the question was for Mac OS X, but I found this page whilst searching for an iOS solution. My answer below doesn't work on Mac OS X as the drawViewHierarchyInRect() API call is currently iOS only, but I put it here for reference for other iOS searchers.

    This Stackoverflow answer solved it for me on iOS 8 with a WKWebView. That answer's sample code is in Objective-C but the Swift equivalent to go in a UIView sub-class or extension would be along the lines of the code below. The code ignores the return value of drawViewHierarchyInRect(), but you may want to pay attention to it.

    func imageSnapshot() -> UIImage
    {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0);
        self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true);
        let snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return snapshotImage;
    }
    
    0 讨论(0)
提交回复
热议问题