ios WKScriptMessageHandler不会侦听网页上按钮元素的“onclick”或“click”事件,该网页是使用Reactjs开发的

wz8daaqr  于 2023-01-06  发布在  iOS
关注(0)|答案(3)|浏览(148)

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

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的代理,接收webview的消息。现在我还实现了一个符合WKScriptMessageHandler的WKWebViewWrapper类。这个类可以接收来自webkit对象的消息,webkit对象是由WKWebView在场景后面创建的。实现方法如下

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参数的消息处理程序添加到webkit对象。触发消息处理程序时,**WKScriptMessageHandler的userContentController(***userContentController:WK用户内容控制器,did接收消息:WKScriptMessage)方法被调用,并带有有用的参数,然后我使用webview的evaluateJavaScript方法将javascript注入到网页中,如下所示

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。
现在,我从WKWebViewDelegate方法webView(_ webView:WKWebView,完成导航:WKNavigation!),在这里我可以通过比较WKNavigation对象来确保所有HTML元素都被加载了。
该流执行,并且在页面加载之后,我单击任何按钮,我的脚本消息处理程序(即WKWebViewWrapper类)不会观察到这些事件。方法
userContentController(_ userContentController:WK用户内容控制器,did接收消息:WKScriptMessage)**根本不会被触发。
这里有什么我错过了吗?。我不擅长Javascript。请让我知道如果Reactjs需要一些不同的脚本和事件侦听器按钮元素。I have reffered this tutorial.

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

pvcm50d1

pvcm50d11#

注意WKWebViewConfigurationApple Docs中的一个重要行为(但不太为人所知),

  • WKWebViewConfiguration仅在首次初始化Web视图时使用。创建Web视图后,不能使用此类更改其配置。*

因此,通常您应该在创建Web视图之前完全设置WKUserContentController

// First, create custom configuration with user script
let userController = WKUserContentController()
let scalingScriptString = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
let scalingScript = WKUserScript(source: scalingScriptString, injectionTime: .atDocumentStart, forMainFrameOnly: true)
userController.addUserScript(scalingScript)

let configurations = WKWebViewConfiguration()
configurations.userContentController = userController  // MUST set controller in configurations before creating webview
// Now, use that configuration to create the webview
webView = WKWebView(frame: .zero, configuration: configurations)
q0qdq0h2

q0qdq0h22#

所以我最终让它工作起来了。有很多问题,但都是由于我的JavaScript错误。JavaScript只是无法执行,而不是产生任何错误,但这让它看起来像是iOS的问题。它花了很长时间和大量的调试通过Safari。
从本质上讲,我发现的可能是一个错误,这是猖獗的,因为有多少文档/文章在线发送消息通过WKMessagingScript.所有的例子显示这样的东西:

window.webkit.messageHandlers.test.postMessage(“message to post”)

他们中的一些人继续说你可以发送任何东西,甚至是JSON字典。他们没有说的是1)你不能将对象传递给这个函数,传递文本字典在JavaScript中是非法的。他们也没有给予更多适用的例子。你可能只需要一个字符串消息来告诉你发生了什么,但是如果你需要传递数据的话,你就需要“我将从元素中获得它,并且很可能在实现中犯第一个错误(你和我都犯过)。
1)是一个大问题,在你的例子中你调用了一个函数,在JavaScript中函数是一级公民,所以你传递的是一个对象,你也不能传递,例如,element.id,这是我所做的,因为你必须传递元素,你需要做的是只传递值,这是一个基础类型。

***请注意,您可以在JavaScript中传递一个对象,例如console.log(element);,这使得调试这个问题非常困难。如果您注解掉WebKit调用,但将函数传递到控制台日志,它将工作,暗示问题出在iOS上,而不是突出显示问题实际上出在传递对象上。

2)我通常会在控制台日志中工作,因为我们使用的浏览器会识别它。足够多的开发人员这样做,即使它不正确,浏览器也会解释它。iOS也可能有一天,但最好不要这样做。
这样就可以工作了(假设代码中没有其他问题):

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

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

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都支持它,我也不建议使用它。
不过,我确实看到了你的代码还有两个问题。第一个问题是,你在应该使用“click "的时候使用了”onClick“。
因此onclick在绑定的HTML标记中创建一个属性,使用一个链接到函数的字符串。而.click将函数本身绑定到属性元素。https://teamtreehouse.com/community/whats-the-difference-between-click-and-onclick
如果你是一个网络开发人员,你会处理这两个,但对于iOS,你应该只使用点击。
我在代码中注意到的另一件事是将eventName传入webkit函数window.webkit.messageHandlers.\(eventName).postMessage.....。我不完全确定这是否有效。我怀疑它不会,因为它不是字符串。这是一个函数调用。虽然我对JavaScript一无所知(这是我写的第一个JS)所以我可能是错的,在objc或者swift中,虽然你不能在调用函数的时候这么做。如果iOS WKMessagingScript被更新为不再允许它,即使它能工作,我认为它也会增加太多的复杂性,并且不可扩展。我建议使用正确的名称。如果你想封装你的代码,请打开eventName

iezvtpos

iezvtpos3#

4年后,我有机会在类似的代码库上工作,并找到了失败的确切原因。确切原因如下:
1.对userContentController不必要且错误的赋值。这不会替换示例化WKWebView时创建的实际WKUserContentController。我们错误地添加了脚本处理程序,如controller.add(self, name: eventname)。相反,我们应该将脚本处理程序直接添加到webviews usercontentcontroller示例,如

//WRONG: To set userContentController and add script handler 
wkWebView.configuration.userContentController = controller
controller.add(self, name: eventname)    

//CORRECT: To add script handler
wkWebView.configuration.userContentController.add(self, name: eventname)

感谢@Ashok指出这一点。
1.评估javascript以向每个元素添加事件侦听器的时间安排并不完美。在我们的javascript得到评估时,元素还没有创建,因此没有添加事件侦听器。有两种方法可以优化javascript的评估时间。我们可以引入一个延迟,这显然不是一个干净的解决方案。更简洁的解决方案是使用WKUserScript,将injectionTime设置为.atDocumentEnd,然后将脚本添加到userContentController

let eventName = "SOME_EVENT"
let js = "var elements = document.getElementsByClassName('btn button_btn button_primary button_md button_block'); for (var i = 0 ; i < elements.length; i++) { elements[i].addEventListener('click', function(){ window.webkit.messageHandlers.\(eventname).postMessage("MESSAGE") }); }"
let userScript = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
wkWebView.configuration.userContentController.addUserScript(userScript)

1.我要感谢@修道院Jackson提供的providing hints to the solution for my case,她的回答告诉我,onClickonclick不应该用于向元素添加事件侦听器,而应该使用click来完成同样的任务。

// Relevant while coding for WKWebView on iOS

// WRONG: Using onClick or onclick does not register the event listener
elements[i].addEventListener("click", function(){ YOUR_HANDLER_CODE } }

// CORRECT: Use click instead of onClick or onclick for adding event listener
elements[i].addEventListener("click", function(){ YOUR_HANDLER_CODE } }

注意:我可以完美地传递一个字面字典,如{“foo”:“bar”}代替了“YOUR_MESSAGE”。它没有给我带来问题。虽然它可能会导致一个问题,如果它包含的项目不能很容易地移植到斯威夫特。

相关问题