WKScriptMessageHandler 不会侦听网页上按钮元素上的“onclick"或“click"事件.网页是使用 Reactjs 开发的 [英] WKScriptMessageHandler won't Listen to 'onclick' or 'click' event on a button element on a webpage. The web page is developed using Reactjs

查看:16
本文介绍了WKScriptMessageHandler 不会侦听网页上按钮元素上的“onclick"或“click"事件.网页是使用 Reactjs 开发的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 UIViewController 的视图中使用 WKWebView 来显示使用 url 端点托管在服务器上的网页.该网页使用 Reactjs.这就是我拥有的有关网页的所有信息.代码创建一个 webview 并将 webview 作为控制器视图的子视图插入.

I am using a WKWebView inside a UIViewController's view to display a webpage hosted on a server using a url endpoint. The webpage uses Reactjs. That is all the information I have about the webpage. The code creates a webview and inserts the webview as subview of the controllers view.

let requestObj = URL(string:urlString)!
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
webViewWK = WKWebView(frame: .zero, configuration: configuration)
webViewWK.navigationDelegate = self
_ = webViewWK.load(requestObj)
webViewwrapper = WKWebViewWrapper(forWebView: webViewWK)

网页加载正常,控制器也充当 webview 的委托并接收相同的消息.现在我还实现了一个符合 WKScriptMessageHandler 的 WKWebViewWrapper 类.然后这个类可以从由 WKWebView 在场景中创建的 webkit 对象接收消息.相同的实现如下

The webpage loads fine and also the controller acts as the delegate of the webview and receives the messages for the same. Now I also implement a WKWebViewWrapper class which conforms to WKScriptMessageHandler. This class can then receive messages from webkit object which is created by the WKWebView behing the scenes. The implementation for the same is as below

class WKWebViewWrapper : NSObject, WKScriptMessageHandler{

var wkWebView : WKWebView
let eventNames = ["buttonClick"]
var eventFunctions: Dictionary<String, (String) -> Void> = [:]
let controller: WKUserContentController

init(forWebView webView : WKWebView){
    wkWebView = webView
    controller = WKUserContentController()
    super.init()
}

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    if let contentBody = message.body as? String {
        if let eventFunction = eventFunctions[message.name]{
            print("Detected javascript event")
        }
    }
}

func setUpPlayerAndEventDelegation(){
    wkWebView.configuration.userContentController = controller
    for eventname in eventNames {
        controller.add(self, name: eventname)
        eventFunctions[eventname] = { _ in }

        wkWebView.evaluateJavaScript("var elements = document.getElementsByClassName('btn button_btn button_primary button_md button_block'); for (var i = 0 ; i < elements.length; i++) { elements[i].addEventListener('onClick', function(){ window.webkit.messageHandlers.\(eventname).postMessage(JSON.stringify(isSuccess)) }); }") { any, error in
            if let error = error {
                print("EvaluateJavaScript Error:",error)
            }

            if let any = any {
                print("EvaluateJavaScript anything:", any)
            }

        }
    }
}

}

setUpPlayerAndEventDelegation() 方法是最重要的部分.这里对于 WKUserContentcontroller 类型的控制器对象,使用其 add(: , name:) 方法添加消息处理程序.根据文档,此方法将 name 参数的 messageHandler 添加到 webkit 对象.每当触发消息处理程序时,都会使用有用的参数调用 WKScriptMessageHandler 的 userContentController( userContentController: WKUserContentController, didReceive message: WKScriptMessage) 方法.然后我使用如下所示的 webview 的evaluateJavaScript 方法将javascript 注入到网页中

The setUpPlayerAndEventDelegation() method is the most important part. Here for the controller object which is of type WKUserContentcontroller adds message handlers using its add(: , name:) method. According to documentation this method adds a messageHandler of the name parameter to the webkit object. Whenver the messsage handler is triggered, the WKScriptMessageHandler's userContentController( userContentController: WKUserContentController, didReceive message: WKScriptMessage) method is called with useful parameters. Then I inject javascript into the webpage using evaluateJavaScript method of webview which is as below

var elements = document.getElementsByClassName('btn button_btn button_primary button_md button_block'); 
for (var i = 0 ; i < elements.length; i++) {
    elements[i].addEventListener('onClick', function(){ window.webkit.messageHandlers.\(eventname).postMessage(JSON.stringify(true)) }); 
}

它获取具有给定类的元素.然后我遍历数组为每个元素的 HTML 事件 'onClick' 添加事件侦听器.对于事件侦听器,我添加了一个匿名函数来触发先前在 webkit 上注册的消息处理程序.这个脚本被正确执行,因为我在 evaluateJavaScript 方法的完成块中没有出现错误.所以我现在可以确定,当一个按钮 onClick HTML 事件发生时,匿名函数将执行,它依次为 webkit 对象上的 messageHandler postMessage.

It fetches elements with the given class. Then I iterate over the array to add event listener for HTML event 'onClick' for each element. For events listener I add an anonymous function to trigger the previously registered message handler on the webkit. This script is executed properly as I don't get error in the completion block of the evaluateJavaScript method. So I can be sure now that when a button onClick HTML event occurs the annonymous function will execute, which in turn will postMessage for the messageHandler on the webkit object.

现在我从 WKWebViewDelegate 方法 webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) 调用 WKWebViewWrapper 的 setUpPlayerAndEventDelegation() 方法,我可以确定所有 HTML 元素都在通过对 WKNavigation 对象进行匹配来加载.

Now I call the WKWebViewWrapper's setUpPlayerAndEventDelegation() method from WKWebViewDelegate method webView(_ webView: WKWebView, didFinish navigation: WKNavigation!), where I can be sure that all the HTML elements are loaded by comapring WKNavigation objects.

流程执行后,页面加载并单击任何按钮后,我的脚本消息处理程序(即 WKWebViewWrapper 类)不会观察到这些事件.方法 userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) 根本没有被触发.

The flow executes and after the Page loads and I click any buttons the events are not observed by my script message handler i.e the WKWebViewWrapper class. The method userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) is not fired at all.

我在这里遗漏了什么吗?.我不擅长 Javascript.请让我知道 Reactjs 是否需要一些不同的脚本和按钮元素的事件侦听器.我推荐了本教程.

Is there something that I am missing here?. I am not good at Javascript. Please do let me know if Reactjs needs some different script to and event listener to button elements. I have reffered this tutorial.

PS:如果我们添加类似的脚本以在已加载页面的网络浏览器上输出控制台消息,它可以正常工作.

推荐答案

所以我实际上最终让它工作了.有很多问题,但它们都是由于我的 JavaScript 中的错误造成的.JavaScript 只是无法执行而不是产生任何错误,但这使它看起来像是 iOS 问题.通过Safari花了很长时间和大量的调试.

So I actually ended up getting it to work. There were a number of issues but they were all due to errors in my JavaScript. The JavaScript simply failed to execute rather than producing any errors however which made it seem like it was an iOS problem. It took a really long time and lots of debugging via Safari.

基本上我发现的可能是一个很普遍的错误,因为通过 WKMessagingScript 发送消息的在线文档/文章很少.所有示例都显示如下内容:

Essentially what I discovered is probably a mistake that is rampant because of how little documentation/articles there are online for sending messages via WKMessagingScript. All of the samples show something like this:

window.webkit.messageHandlers.test.postMessage("message to post")

他们中的一些人继续说你可以发送任何东西,甚至是 json 字典.他们没有说的是 1) 您不能将对象传递给此函数,以及 2) 传递文字字典在 JavaScript 中是非法的.他们也没有给出更多适用的例子.您可能只需要一个字符串消息来让您知道发生了一些事情,但是如果您需要传递数据,您将从元素中获取它,并且可能会在您的实现中犯第 1 号错误(您和我都做了什么).

Some of them go on to say you can send anything even a json dictionary. What they fail to say is that 1) you can NOT pass an object to this function, and 2) passing a literal dictionary is illegal in JavaScript. They also don’t give any more applicable examples. You may want just a string message to let you know something has happened but if you need to pass data you’ll be getting it from the elements and are likely to make mistake number 1 in your implementation (what both you and I did).

1) 是一个大问题.在您的示例中,您正在调用一个函数.在 JavaScript 中,函数是一等公民,因此您传递的是一个对象.例如,您也不能传递 element.id ,这是我正在做的,因为您必须传递元素.您需要做的是仅传递作为基础类型的值.

1) is a biggie. In your example you are calling a function. In JavaScript functions are first class citizens so you are passing an object. You also can not, for example, pass element.id which is what I was doing because you have to pass the element. What you need to do is pass the value only which is a foundation type.

*** 注意,您可以在 JavaScript 中传递一个对象,例如 console.log(element);,这使得调试这个问题变得如此困难.如果您已注释掉 WebKit 调用但将您的函数传递给控制台日志它会起作用,这意味着问题出在 iOS 上,而不是突出显示问题实际上是传递一个对象.

*** Note you can pass an object within JavaScript such as console.log(element); which is what makes debugging this issue so hard. If you had commented out the WebKit call but passed your function to a console log it would have worked, implying the problem was with iOS, rather than highlighting the problem was actually with passing an object.

2) 通常在控制台日志中工作,因为我们使用的浏览器会识别它.有足够多的开发人员这样做,即使浏览器会解释它是不正确的.iOS 也可能有朝一日,但最好不要这样做.

2) will usually work in console logging because the browsers we use will recognize it. Enough devs do it that even though it’s not right the browsers will interpret it. iOS may also one day too but it’s better practice to not do it.

这会奏效(假设您的代码中没有其他问题):

This would have worked (assuming no other issues in your code):

var elements = document.getElementsByClassName('btn button_btn button_primary button_md button_block'); 
for (var i = 0 ; i < elements.length; i++) {
    var message = String(JSON.stringify(true));
    elements[i].addEventListener('click', function(){ 
        window.webkit.messageHandlers.testEvent.postMessage(message) 
    }); 
}

现在我不完全确定单独发送字符串是否仍然有效,或者它是否需要像我发送字典一样是字典,但如果您需要发送字典,您可以这样做:

Now I'm not totally sure if sending a string alone will still work or if it needs to be a dictionary as I was sending dictionaries but if you need to send a dictionary you would do it like this:

var elements = document.getElementsByClassName('btn button_btn button_primary button_md button_block'); 
for (var i = 0 ; i < elements.length; i++) {
    var eventName = String(eventName); // if this variable is a string then you probably don't need this step
    var stringified = String(JSON.stringify(true));
    var message = {};
    message[String(eventName)] = stringified;
    elements[i].addEventListener('click', function(){ 
        window.webkit.messageHandlers.testEvent.postMessage(message) 
    }); 
}

而不是 window.webkit.messageHandlers.testEvent.postMessage({"foo": "bar"}) 例如.请注意,我没有测试这不起作用,我只是在网上阅读并询问了我认识的 JS 开发人员,他们确认了这一点,所以谁知道它可能有效.我认为把它分开更安全,但以防万一.使用方括号将一些速记添加到 JS 中,允许您传递文字,但它是最近才添加的,所以我不认为所有版本的 iOS 都支持它,我不建议使用它.

Rather than window.webkit.messageHandlers.testEvent.postMessage({"foo": "bar"}) for example. Note that I didn't test that this did not work, I just read this online and asked a JS dev I know and they confirmed it so who knows it may work. I think it's safer to break it up though just in case. There is some shorthand that has been added to JS using square brackets that would allow you to pass a literal however it is only recently added so I don't imagine all versions of iOS support it and I would not recommend using it.

不过,我确实发现您的代码存在另外两个问题.首先是您在应该使用点击"时使用了onClick".

I do see two additional problems with your code though. First is that you are using 'onClick' when you should be using 'click'.

所以 onclick 在绑定的 HTML 标签中创建一个属性,使用链接到函数的字符串.而 .click 将函数本身绑定到属性元素.https://teamtreehouse.com/community/whats-the-点击和点击之间的区别

So onclick creates an attribute within the binded HTML tag, using a string which is linked to a function. Whereas .click binds the function itself to the property element. https://teamtreehouse.com/community/whats-the-difference-between-click-and-onclick

如果您是 Web 开发人员,您会同时处理这两种情况,但对于 iOS,您应该只使用 click.

If you were a web dev you would handle both but for iOS you should only use click.

我在您的代码中注意到的另一件事是您将 eventName 传递到 webkit 函数 window.webkit.messageHandlers.\(eventName).postMessage......我不完全确定这是否有效.我怀疑它不会,因为那不是字符串,而是函数调用.虽然我对 JavaScript 一无所知(这实际上是我写过的第一个 JS),所以我可能是错的.在 objc 或 swift 中,尽管在进行函数调用时你不能这样做.即使它可以工作,我认为如果 iOS WKMessagingScript 更新为不再允许它,它会增加太多复杂性并且不可扩展.我建议使用正确的名称.如果您想封装您的代码,请打开 eventName.

The other thing I noticed in your code is you are passing the eventName into the webkit function window.webkit.messageHandlers.\(eventName).postMessage...... I'm not totally sure if that will work or not. I suspect it will not because that is not a string, that is a function call. Though I don't know anything about JavaScript at all (this was literally the first JS I've ever written) so I may be wrong about that. In objc or swift though you could not do that when making a function call. Even if it would work I think it adds too much complexity and is not scalable if the iOS WKMessagingScript were updated to no longer allow it. I would suggest using the correct name. If you want to encapsulate your code then switch on eventName.

这篇关于WKScriptMessageHandler 不会侦听网页上按钮元素上的“onclick"或“click"事件.网页是使用 Reactjs 开发的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆