I am trying to call native function in swift from wkwebview. This is what I have done so far:
Swift Part
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
contentController.add(self, name: "backHomePage")
config.userContentController = contentController
self.webView = WKWebView(frame: self.containerView.bounds, configuration: config)
webView.navigationDelegate = self
self.containerView.addSubview(self.webView)
if let url = URL(string: assessmentLink) {
webView.load(URLRequest(url: url))
}
}
And
extension WebFormVC: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("javascript sending \(message.name), body: \(message.body)")
}
}
Javascript
function backHomePage(message) {
window.webkit.messageHandlers.backHomePage.postMessage(message);
}
where message can be any string for example: "success"
I am not currently receiving call back in userContentController didReceive method
UPDATE
I also tried sending data as key value for example window.webkit.messageHandlers.backHomePage.postMessage({"message" :"Javascript to swift!"});
, and it still didn't work.
I've tried to reproduce your case and everything seems to work as expected
import UIKit
import WebKit
class ViewController: UIViewController, WKScriptMessageHandler {
let content = """
<!DOCTYPE html><html><body>
<button onclick="onClick()">Click me</button>
<script>
function onClick() {
window.webkit.messageHandlers.backHomePage.postMessage("success");
}
</script>
</body></html>
"""
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.userContentController = WKUserContentController()
config.userContentController.add(self, name: "backHomePage")
let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 200, height: 200), configuration: config)
view.addSubview(webView)
webView.loadHTMLString(content, baseURL: nil)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.body)
}
}
Okay so the issue in my case was the call to window.webkit.messageHandlers.backHomePage.postMessage(message); was inside the .js file which was loaded externally and I dont really know why it was not being called.
I solved this issue by injecting an overload of the javascript function, keeping the body same as it is in that external .js file, and injected it at the end of the Document.
Thanks to #Andy for suggesting the idea of injecting userScript in the document. This information might come handy if anyone else faces same issue but is not the answer to the question "How to receive callback in ios from javascript?" so #Andy's answer is accepted since it precisely answers the question.
Related
I want javascript to send a message back to my WKWebView object, but I get nothing in response. I know the script is running, as the color changes, but I am expecting to also see "trigger from JS" printed in the console, which I don't. If I run the html in Chrome, the javascript console says "Cannot read property 'messageHandlers' of undefined". If I build for iOS (using UIViewRepresentable, MakeUIView and UpdateUIView) the result is the same. If anyone can spot what I have missed I would greatly appreciate it.
This is the entirety of the code:
import SwiftUI
import WebKit
class HtmlData {
let html = """
<!DOCTYPE html>
<html>
<body>
<button onclick="sendMessage()">Send Message</button>
<script>
function sendMessage() {
document.body.style.backgroundColor = "red";
window.webkit.messageHandlers.testMessage.postMessage("trigger from JS");
}
</script>
</body>
</html>
"""
}
struct ContentView: View {
let htmlData = HtmlData()
var body: some View {
JSWebView(html: htmlData.html)
}
}
struct JSWebView: NSViewRepresentable {
let html: String
func makeNSView(context: Context) -> WKWebView {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
return WKWebView()
}
func updateNSView(_ view: WKWebView, context: Context) {
let userContentController = WKUserContentController()
let handler = ContentController(view)
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
configuration.userContentController.add(handler, name: "testMessage")
view.loadHTMLString(html, baseURL: Bundle.main.bundleURL)
}
class ContentController: NSObject, WKScriptMessageHandler {
var parent: WKWebView?
init(_ parent: WKWebView?) {
self.parent = parent
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.body)
}
}
}
Configuration must be passed into the constructor of WKWebView. It can't be set after initialization.
struct JSWebView: NSViewRepresentable {
let html: String
func makeNSView(context: Context) -> WKWebView {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let handler = MessageHandler()
let configuration = WKWebViewConfiguration()
configuration.userContentController.add(handler, name: "testMessage")
return WKWebView(frame: .zero, configuration: configuration)
}
func updateNSView(_ view: WKWebView, context: Context) {
view.loadHTMLString(html, baseURL: Bundle.main.bundleURL)
}
class MessageHandler: NSObject, WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.body)
}
}
}
Please tell me, there is an ios application, it contains WebKitView, and in it there is a site with this code:
<div id="test" style="height: 40px; width: 100px; background-color: powderblue;">Hello</div>
<script type="text/javascript">
document.getElementById("test").addEventListener("click", function () {
window.webkit.messageHandlers.test.postMessage("TEXT");
});
</script>
Application Code:
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://site")!
webView.load(URLRequest(url: url))
let config: WKWebViewConfiguration = WKWebViewConfiguration()
config.userContentController.add(self, name: "test")
webView = WKWebView(frame: self.view.frame, configuration: config)
webView?.navigationDelegate = self
self.webView?.load(URLRequest(url:url))
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "test", let messageBody = message.body as? String {
print(messageBody)
}
}
}
When you click on the div in xcode, the following error is displayed in the log
[general] Connection to daemon was invalidated
What could be the problem?
Sorry for my English.
I've added the shared HTML & javascript code in the main bundle as "example.html" and made changes in your code. It is working fine at my end.
override func viewDidLoad() {
super.viewDidLoad()
//Url from the html
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "example", ofType: "HTML") ?? "")
// Configuring WKEWebview
let config: WKWebViewConfiguration = WKWebViewConfiguration()
config.userContentController.add(self, name: "test")
webView = WKWebView(frame: self.view.frame, configuration: config)
webView?.navigationDelegate = self
//Constraints related changes
webView.translatesAutoresizingMaskIntoConstraints = true
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(self.webView)
// Load file
self.webView?.loadFileURL(url, allowingReadAccessTo: Bundle.main.bundleURL)
}
You can load your webpage using
self.webView.load(URLRequest(url: "https://www.site"))
Try it and let me know if you face any issue.
WKWebView not able to trigger javascript in the loaded Web Page.
Scenario:
If user click image in Website, it should get update.
Using javascript to update the image on the website, if the user clicks a image.
included .js file in project
configured WKWebview
Enabled JavaScript
Added Script in WKWebview
Function in JS file like :
function captureImage(bucket,fileName){
window.webkit.messageHandlers.captureImage.postMessage("showCamera")
}
Accessing this function in Swift like:
webViewPWA.configuration.userContentController.add(self, name: "captureImage")
///This function handles the event generated by javascript
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("Webview Message received: \(message.name) with body: \(message.body)")
if (message.name == "captureImage"){
print("\(message.body)")
let body = message.body
if let action:String = body as? String {
switch action {
case "showCamera":
print("camera image triggering, capture image for JS")
//take necessary action
break
default:
break
}
}
}
return
}
Thanks in advance!
Once the picture is captured on the device in UIImagePickerControllerDelegate method like:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
imagePicker.dismiss(animated: true, completion: nil)
imageView.image = info[.originalImage] as? UIImage
}
You can run any JS you want in a WKWebView by using evaluateJavaScript() and get a result in Swift.
webView.evaluateJavaScript("document.getElementById('someElement').innerText") { (response, error) in
guard error == nil else {
print("evaluateJavaScript error -- \(String(describing: error))")
return
}
print("evaluateJavaScript response -- \(String(describing: response))")
}
it would also be nice to have something similar to WebBridge.js that provides functions for communicating with the build ing WKWebView in iOS and he android webview
and inside of the WebBridge.js you can define:
/* install global handler for WebView to call methods */
if (!window.WebBridge) {
window.WebBridge = (function () {
var actions = []
return {
receive: function (actionName, json) {
if (typeof actions[actionName] !== 'undefined') {
actions[actionName](json)
}
},
registerActionHandler: function (actionName, method) {
actions[actionName] = method
}
}
})()
}
then from Swift file you can narrow down the structure of your JS calls:
self.webView.evaluateJavaScript("WebBridge.receive('yourCustomActionName', '{\"yourRey\": \"yourValue\"}')") { (response, error) in
guard error == nil else {
print("evaluateJavaScript error -- \(String(describing: error))")
return
}
print("evaluateJavaScript response -- \(String(describing: response))")
}
In android i use this
https://developer.android.com/reference/android/webkit/JavascriptInterface
to catch the events javascript in java
but in swift, what can i use ?
i just found to execute javascript for swift, but i want catch a click event.
Example, when the user click in button, change the screen according the option selected.
You can use WKWebView for this
let contentController = WKUserContentController()
contentController.add(self, name: scriptMessageName)
let config = WKWebViewConfiguration()
config.userContentController = contentController
let webView = WKWebView(frame: self.view.frame, configuration: config)
Then confirm you ViewController to WKScriptMessageHandler, WKNavigationDelegate
extension ViewController: WKScriptMessageHandler, WKNavigationDelegate {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case scriptMessageName:
someAction()
default:
break
}
}
}
I am new to Swift/iOS and have been working on an app that calls Swift code from JavaScript. There are not as many tutorials online and a lot of them are from when Swift was still in beta. Anyway, I was getting an error on my code and I am unable to compile it and I was wondering if any one had any tips and best practices when it comes to calling swift code from JavaScript.
Here is my code
import UIKit
import WebKit
class ViewController: UIViewController, WKScriptMessageHandler {
#IBOutlet var containerView : UIView! = nil
var webView: WKWebView?
override func loadView() {
super.loadView()
var contentController = WKUserContentController();
var userScript = WKUserScript(
source: "redHeader()",
injectionTime: WKUserScriptInjectionTime.AtDocumentEnd,
forMainFrameOnly: true
)
contentController.addUserScript(userScript)
contentController.addScriptMessageHandler(
self,
name: "callbackHandler"
)
var config = WKWebViewConfiguration()
config.userContentController = contentController
self.webView = WKWebView(frame: self.view.frame, configuration: config)
self.view = self.webView!
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var url = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("index", ofType: "html")!)
var req = NSURLRequest(URL: url!)
self.webView!.loadRequest(req)
}
func userContentController(userContentController: WKUserContentController!,didReceiveScriptMessage message: WKScriptMessage!) {
if(message.name == "callbackHandler") {
println("JavaScript is sending a message \(message.body)")
} }
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The error is on class ViewController: UIViewController, WKScriptMessageHandler { and it says
Type 'ViewController' does not conform to protocol 'WKScriptMessageHandler'.
Any help would be greatly appreciated.
I note that your method userContentController is:
func userContentController(userContentController: WKUserContentController!,
didReceiveScriptMessage message: WKScriptMessage!)
it should be:
func userContentController(userContentController: WKUserContentController,
didReceiveScriptMessage message: WKScriptMessage)
(no optionals ! in prototype).
If you need more help, please post your index.html source file