SwiftUI: Javascript communication with WKWebView on macOS - javascript

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)
}
}
}

Related

callback javascript postMessage ios swift

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.

How to receive callback in ios from javascript?

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.

WKUserContentController isn't called when Javascript calls handler

I'm developing IOS App using Webkit, Swift.
And I wanna close view controller which shows webkit when my webview logic is done. Using WKUserContentController, I implemented logic.
But It didn't work.
Below is my code.
This is Swift.
import UIKit
import WebKit
class SelfAuthVC: UIViewController, WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler {
private var webView: WKWebView!
let contentController = WKUserContentController()
let config = WKWebViewConfiguration()
override func viewWillAppear(_ animated: Bool) {
contentController.add(self as WKScriptMessageHandler, name: "callbackHandler")
config.userContentController = contentController
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
config.preferences = preferences
webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)
let url = Bundle.main.url(forResource: "auth", withExtension: "html")!
webView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
webView.load(request)
webView.navigationDelegate = self
webView.uiDelegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
}
func webViewDidClose(_ webView: WKWebView) {
self.navigationController?.popViewController(animated: true)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("var my_email = '\(UserInfo.shared().email!)'") { (result, error) in
guard error == nil else {
print(error)
return
}
}
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("didReceive message here")
if(message.name == "callbackHandler"){
print("callbackHandler: \(message.body)")
self.dismiss(animated: true, completion: nil)
self.navigationController?.popViewController(animated: true)
}
}
}
Below is JS
<body>
<script language="JavaScript">
IMP.certification({
min_age: 18
}, function(rsp) {
if ( rsp.success ) {
$.ajax({
method : "POST",
url : "https://******/user/iamport",
dataType : 'json',
data : {
imp_uid : rsp.imp_uid,
user_email: my_email
}
}).done(function(rsp) {
// 이후 Business Logic 처리하시면 됩니다.
(function finish(){
window.webkit.messageHandlers.callbackHandler.postMessage("trigger from JS");
try {
webkit.messageHandlers.callbackHandler.postMessage(
'done'
);
} catch(err) {
console.log('The native context does not exist yet');
}
}())
});
} else {
// Auth failed
var msg = '인증에 실패하였습니다. 관리자에게 문의하세요';
msg += '에러내용 : ' + rsp.error_msg;
alert(msg);
}
});
</script>
</body>
I think I wrote codes that is needed.
But after authorization process, ViewContoller don't disappear.
What changes are needed to solve this problem?
I forgot to add this codes.
config.userContentController = contentController
contentController.add(self, name: "authAction")

Catch events javascript in swift

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
}
}
}

Calling Swift from JavaScript

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

Categories

Resources