我在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浏览器上输出控制台消息,它可以正常工作。
3条答案
按热度按时间pvcm50d11#
注意
WKWebViewConfiguration
在Apple Docs中的一个重要行为(但不太为人所知),因此,通常您应该在创建Web视图之前完全设置
WKUserContentController
。q0qdq0h22#
所以我最终让它工作起来了。有很多问题,但都是由于我的JavaScript错误。JavaScript只是无法执行,而不是产生任何错误,但这让它看起来像是iOS的问题。它花了很长时间和大量的调试通过Safari。
从本质上讲,我发现的可能是一个错误,这是猖獗的,因为有多少文档/文章在线发送消息通过WKMessagingScript.所有的例子显示这样的东西:
他们中的一些人继续说你可以发送任何东西,甚至是JSON字典。他们没有说的是1)你不能将对象传递给这个函数,传递文本字典在JavaScript中是非法的。他们也没有给予更多适用的例子。你可能只需要一个字符串消息来告诉你发生了什么,但是如果你需要传递数据的话,你就需要“我将从元素中获得它,并且很可能在实现中犯第一个错误(你和我都犯过)。
1)是一个大问题,在你的例子中你调用了一个函数,在JavaScript中函数是一级公民,所以你传递的是一个对象,你也不能传递,例如,
element.id
,这是我所做的,因为你必须传递元素,你需要做的是只传递值,这是一个基础类型。***请注意,您可以在JavaScript中传递一个对象,例如
console.log(element);
,这使得调试这个问题非常困难。如果您注解掉WebKit调用,但将函数传递到控制台日志,它将工作,暗示问题出在iOS上,而不是突出显示问题实际上出在传递对象上。2)我通常会在控制台日志中工作,因为我们使用的浏览器会识别它。足够多的开发人员这样做,即使它不正确,浏览器也会解释它。iOS也可能有一天,但最好不要这样做。
这样就可以工作了(假设代码中没有其他问题):
现在我不确定单独发送一个字符串是否仍然有效,或者它是否需要像我发送字典一样是一个字典,但是如果你需要发送字典,你可以这样做:
而不是
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
。iezvtpos3#
4年后,我有机会在类似的代码库上工作,并找到了失败的确切原因。确切原因如下:
1.对
userContentController
不必要且错误的赋值。这不会替换示例化WKWebView
时创建的实际WKUserContentController
。我们错误地添加了脚本处理程序,如controller.add(self, name: eventname)
。相反,我们应该将脚本处理程序直接添加到webviews usercontentcontroller示例,如感谢@Ashok指出这一点。
1.评估javascript以向每个元素添加事件侦听器的时间安排并不完美。在我们的javascript得到评估时,元素还没有创建,因此没有添加事件侦听器。有两种方法可以优化javascript的评估时间。我们可以引入一个延迟,这显然不是一个干净的解决方案。更简洁的解决方案是使用
WKUserScript
,将injectionTime
设置为.atDocumentEnd
,然后将脚本添加到userContentController
1.我要感谢@修道院Jackson提供的providing hints to the solution for my case,她的回答告诉我,
onClick
、onclick
不应该用于向元素添加事件侦听器,而应该使用click
来完成同样的任务。注意:我可以完美地传递一个字面字典,如{“foo”:“bar”}代替了“YOUR_MESSAGE”。它没有给我带来问题。虽然它可能会导致一个问题,如果它包含的项目不能很容易地移植到斯威夫特。