6

I'm trying to capture the window.print() javascript call from a WKWebView with my app to actually show a print dialog and allow them to print from a button presented on the page (instead of a button inside my app). Does anyone know the best way to accomplish this?

Currently, for me, clicking a link that calls window.print() just does nothing but fire the decidePolicyForNavigationAction delegate method and I couldn't find anything relevant in there.

Steve E
  • 712
  • 10
  • 16

2 Answers2

11

Figured it out just after posting (obviously)... Hopefully this helps someone else.

When creating your web view...

let configuration = WKWebViewConfiguration()
let script = WKUserScript(source: "window.print = function() { window.webkit.messageHandlers.print.postMessage('print') }", injectionTime: WKUserScriptInjectionTime.AtDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(script)
configuration.userContentController.addScriptMessageHandler(self, name: "print")
self.webView = WKWebView(frame: CGRectZero, configuration: configuration)

Then just adopt the WKScriptMessageHandler protocol in your view controller and add the required method...

func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
    if message.name == "print" {
        printCurrentPage()
    } else {
        println("Some other message sent from web page...")
    }
}

func printCurrentPage() {
    let printController = UIPrintInteractionController.sharedPrintController()
    let printFormatter = self.webView.viewPrintFormatter()
    printController?.printFormatter = printFormatter

    let completionHandler: UIPrintInteractionCompletionHandler = { (printController, completed, error) in
        if !completed {
            if let e = error? {
                println("[PRINT] Failed: \(e.domain) (\(e.code))")
            } else {
                println("[PRINT] Canceled")
            }
        }
    }

    if let controller = printController? {
        if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
            controller.presentFromBarButtonItem(someBarButtonItem, animated: true, completionHandler: completionHandler)
        } else {
            controller.presentAnimated(true, completionHandler: completionHandler)
        }
    }
}
Steve E
  • 712
  • 10
  • 16
0

there is an undocumented delegate for hooking window.print()s

class MyApp: WKUIDelegate {
  func makeWebview() {
      ...
      self.webview.UIDelegate = self
  }
  func _webView(webView: WKWebView!, printFrame: WKFrameInfo) {
      println("JS: window.print()")
      printCurrentPage()
  }     
}
kfix
  • 578
  • 3
  • 12
  • Seems strange that they exposed the delegate method for creating a new window but kept this one private. Hopefully they change this in the future. If you don't need your app to be approved by apple, this is probably a good way to go. – Steve E Feb 09 '15 at 02:27
  • 1
    WebView "private" methods aren't so private, plenty of people use them in App Store submissions and they even support the underscored function signatures after promoting them to public methods. Case in point, Apple has added a new private method to WKWebView in iOS9/OSX10.11: _printOperationWithPrintInfo: https://github.com/WebKit/webkit/commit/0dfc67a174b79a8a401cf6f60c02150ba27334e5 – kfix Jan 05 '16 at 16:05