I am developing a swift ios app that uses WKWebView
to load up an ecommerce site.
When a user purchases a product here, the checkout page allows the user to pay in cryptocurrency.
When the user clicks "Open in Wallet", the site shoots off a
window.postMessage(paymentData)
where payment data is a js object with a bitcoin url in it.
I am using a WKUserScript
with WKWebConfiguration
to inject a script that listens for a window message and then fires off data to my webkit.messageHandler.
let source = """
window.addEventListener('message', function(e) { window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data)) } )
"""
Unfortunately this code never triggers.
When I use chrome or safari devtools to inject the same javascript, it works just fine.
I have scoured stack overflow to see if there is a special condition for window.postMessage
in WKWebView
but have had no luck thus far.
Is it possible to capture a window.postMessage()
event and pipe the event data back to my ios app?
Thanks in advance!!!! Here is my existing code.
let webConfiguration = WKWebViewConfiguration()
let source = """
window.addEventListener('message', function(e) { window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data)) } )
"""
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
userContentController.addUserScript(script)
userContentController.add(self, name: "iosListener")
webConfiguration.userContentController = userContentController
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.navigationDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("message body: \(message.body)")
print("message frameInfo: \(message.frameInfo)")
}
I am developing a swift ios app that uses WKWebView
to load up an ecommerce site.
When a user purchases a product here, the checkout page allows the user to pay in cryptocurrency.
When the user clicks "Open in Wallet", the site shoots off a
window.postMessage(paymentData)
where payment data is a js object with a bitcoin url in it.
I am using a WKUserScript
with WKWebConfiguration
to inject a script that listens for a window message and then fires off data to my webkit.messageHandler.
let source = """
window.addEventListener('message', function(e) { window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data)) } )
"""
Unfortunately this code never triggers.
When I use chrome or safari devtools to inject the same javascript, it works just fine.
I have scoured stack overflow to see if there is a special condition for window.postMessage
in WKWebView
but have had no luck thus far.
Is it possible to capture a window.postMessage()
event and pipe the event data back to my ios app?
Thanks in advance!!!! Here is my existing code.
let webConfiguration = WKWebViewConfiguration()
let source = """
window.addEventListener('message', function(e) { window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data)) } )
"""
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
userContentController.addUserScript(script)
userContentController.add(self, name: "iosListener")
webConfiguration.userContentController = userContentController
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
webView.navigationDelegate = self
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("message body: \(message.body)")
print("message frameInfo: \(message.frameInfo)")
}
Share
Improve this question
edited Jun 21, 2019 at 7:56
Kaushik Makwana
1,3272 gold badges14 silver badges24 bronze badges
asked Jun 20, 2019 at 19:03
MFD3000MFD3000
8741 gold badge13 silver badges26 bronze badges
7
|
Show 2 more comments
2 Answers
Reset to default 14Yes, it is possible. You also need to set javascriptEnabled = true
self.webView.configuration.preferences.javaScriptEnabled = true
You can also configure the listener like this:
self.webView.configuration.userContentController.add(self, name: "iosListener")
And make sure you're applying both commands before
self.webView.load(/*some request*/)
You can make a simple test after the page didFinish
loading with:
self.webView.evaluateJavaScript("window.webkit.messageHandlers.iosListener.postMessage('test');", completionHandler: { (result, err) in
if (err != nil) {
// show error feedback to user.
}
})
Another advice is to always have ;
at the end of commands on javascript code when interacting with webView
as some can rely on standard javascript.
let source = """
window.addEventListener('message', function(e) {
window.webkit.messageHandlers.iosListener.postMessage(JSON.stringify(e.data));
});
"""
Note: I'd also suggest to have the webView
as a class variable instead of a method variable, you're probably creating it on viewDidLoad()
, I'd suggest that you move the variable to your class.
var webView: WKWebView!
Here is what I did to get this working for me. I needed to display a registration page to the user in my app and that page calls window.opener.postMessage()
to send data back to the original web page, but in this case I wanted it to go my view controller instead.
The key thing for me was to open the registration URL with javascript via window.open
so that the WKWebView
would call my delegate method webView(..., createWebViewWith ...)
which returns a new WKWebView
. The WKWebView
will correctly set the window.opener
so that the registration page can post messages back to my webpage which then sends the data to swift.
class GPRegisterDeviceWebViewController: UIViewController, WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate {
let webView = WKWebView(forAutoLayout: ())!
let registrationURL = "https://registrationURL"
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onCancel))
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
//We need to set the uiDelegate so that we can respond to webView(..., createWebViewWith ...)
//We can't just open the registration URL becase that page calls the javascript
//window.opener.postMessage("SOME MSG");
//WKWebView will set the window.opener if you go through the delegate method createWebViewWith
webView.uiDelegate = self
webView.configuration.defaultWebpagePreferences.allowsContentJavaScript = true
//For debugging, allow safari to inspect the page
if #available(iOS 16.4, *) {
webView.isInspectable = true
}
//This uses PureLayout to set up the views layout constraints
webView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
//We want to let this webview call back into our swift code. To do this we need to
//add this message handler. So now in our javascript code we can call
//window.webkit.messageHandlers.myEvent.postMessage("SOME MSG");
//and it will call
//userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
//on our view controller
webView.configuration.userContentController.add(self, name: "myEvent")
//This is the base web page that just receives the message from the registration page.
let html = """
<html>
<head><script type="text/javascript">
function receiveMessage(event) {
window.webkit.messageHandlers.myEvent.postMessage("Send data to swift");
}
function onPageLoad() {
//We need to set up the message event listener.
//The registration page will call window.opener.postMessage() which
//will call receiveMessage on our page.
window.addEventListener("message", receiveMessage, false);
}
</script></head>
<body onload="onPageLoad();">
</body>
</html>
"""
//It is VERY important that this has the baseURL set. We get an error saying that it
//can't post to any other orgin if we don't do this.
webView.loadHTMLString(html, baseURL: URL(string: "https://myBaseURL"))
//Now we need to open the registration URL. It is VERY important that we go through the javascript window.open
//to do this and that we don't just create the view here.
//We need to do this so that WKWebView will set the window.opener by going through the delegate method createWebViewWith
self.webView.evaluateJavaScript("window.open('\(registrationURL)');")
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
let popupWebView = WKWebView(frame: .zero, configuration: configuration)
popupWebView.translatesAutoresizingMaskIntoConstraints = false
popupWebView.navigationDelegate = self
popupWebView.uiDelegate = self
popupWebView.configuration.defaultWebpagePreferences.allowsContentJavaScript = true
if #available(iOS 16.4, *) {
//For debugging
popupWebView.isInspectable = true
}
self.view.addSubview(popupWebView)
//This uses PureLayout to set up the views layout constraints
popupWebView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
//VERY IMPORTANT! We must return the view to set window.opener
return popupWebView
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "myEvent" {
if let body = message.body as? String {
//body contains the data posted from our javascript function receiveMessage()
}
}
}
func webViewDidClose(_ webView: WKWebView) {
self.navigationController?.dismiss(animated: true, completion: nil)
}
@objc func onCancel() {
self.navigationController?.dismiss(animated: true, completion: nil)
}
}
window.postMessage(JSON.stringify(data), '*');
that we cannot capture. If you were interested, I could share the url of the site with you. Thanks again. I upvoted your answer. – MFD3000 Commented Jul 11, 2019 at 17:09injectionTime: .atDocumentStart
– Janno Teelem Commented Oct 3, 2019 at 15:34atDocumentStart
. It would be interesting if this answer would work for the OP. – testing Commented Nov 18, 2019 at 15:53