Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WKWebView notification when view is actually shown

I want to take a screenshot of a WKWebView. I call the method drawViewHierarchyInRect (with screen updates set to true) right after the web view finished loading - for this purpose I observe the loading property of the web view. However, I noticed, that at the time the web view notifies me that loading has finished, it doesn't actually display itself on the screen. That's why the screenshot is always only white. When I take the screenshot 0.5 seconds after loading (which is obviously too long) the screenshot shows the desired result. My problem is: I don't know when the web view actually displays itself on the screen, I could set the delay to 0.05 probably but I can't be a 100% sure it works every time. My question is therefore: How can I be notified when the web view is actually displayed an is ready for a screenshot.

Thanks in advance!

BTW: I'm using Swift 2.0 and iOS 9 with Xcode 7 (official release)

like image 333
borchero Avatar asked Sep 18 '15 19:09

borchero


1 Answers

as I've just worked on this, let me know what I tried and how I figured it out. I tried a couple of things, first, a WKUserScript

let webViewUserScript = WKUserScript(
        source: "window.onload = function() { webkit.messageHandlers.status.postMessage(\(wkwebView.hashValue)) }",
        injectionTime: WKUserScriptInjectionTime.AtDocumentEnd,
        forMainFrameOnly: true
    )

on the "status" namespace. This theoretically waits for all contents to be loaded, and then calls the WKScriptMessageHandler

func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage){ }

when registered via

wkwebView.configuration.userContentController.addScriptMessageHandler(self, name: "status")

where I thought everything would be ready. Worked sometimes, but not always.

As you, I've also tried adding a WKNavigationDelegate and using

func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) { }

Again, this didn't work. Also, adding an observer for the estimatedProgress value

wkwebView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil)

and then trying to make the screenshot within

override func observeValueForKeyPath(keyPath: String?, ofObject: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { }

didn't work.

However, what does work is to check upon the webView's content size regularly. I've did this within the userContentController function, which is called by the WKUserScript - but you can also do this within the didFinishNavigation function, and then simply change the function which is to be called again. Here the code:

let _contentSize = webview.scrollView.contentSize;

if (_contentSize.height == 0){          
  dispatch_after(createDispatchTime(0.01), dispatch_get_main_queue()) {
                self.userContentController(userContentController, didReceiveScriptMessage: message)
  }
  return
}

This essentially checks on the contentSize height property and if it's zero, it will call the method again every 0.01 seconds until the property is set. Here are the functions I used for creating the screenshot

var view : UIView!

if self.useSnapShot {
   // Use the snapshot function
   view = webView.snapshotViewAfterScreenUpdates(true)

} else {
   // Draw the view as UIImage
   UIGraphicsBeginImageContextWithOptions(webView.frame.size, false, 0.0)
   webView.drawViewHierarchyInRect(webView.frame, afterScreenUpdates: true)
   let snapshot = UIGraphicsGetImageFromCurrentImageContext()
   view = UIImageView(image: snapshot)
   view.frame = webView.frame
}

I've tested it on both device and simulator and it works.

Edit

Here the function for creating the dispatch time

private func createDispatchTime(seconds: Double) -> dispatch_time_t{
    let delay = seconds * Double(NSEC_PER_SEC)
    return dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
}

Edit #2

I created a gist which shows an even better way I believe https://gist.github.com/christopherhelf/1640454bcace39a87c93#file-wkwebviewplus-swift

I replaced the setContentSize function of the webviews UIScrollview and notify the parent class that a change has been made.

like image 162
peacer212 Avatar answered Sep 18 '22 15:09

peacer212