无法将 JS 注入 Swift/Cocoa/NextStep 中的 WKWebView / 将 WKWebView 中网页上的用户选择推送到 Swift / Cocoa
Unable to inject JS into WKWebView in Swift/Cocoa/NextStep / Push user selection on web page in WKWebView to Swift / Cocoa
我正在使用一个 MacOS 应用程序,它需要使用 WKUserScript 功能将消息从网页发送回 MacOS 应用程序。我正在研究文章 https://medium.com/capital-one-tech/javascript-manipulation-on-ios-using-webkit-2b1115e7e405,它显示了这个在 iOS 中的工作并且工作得很好。
然而,我已经努力了几个星期,试图让它在我的 MacOS 中运行。这是我的代码示例,它符合要求并运行但没有成功打印在处理程序 userContentController()
中找到的消息
import Cocoa
import WebKit
class ViewController: NSViewController, WKNavigationDelegate {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let userContentController = WKUserContentController()
// Add script message handlers that, when run, will make the function
// window.webkit.messageHandlers.test.postMessage() available in all frames.
userContentController.add(self, name: "test")
// Inject JavaScript into the webpage. You can specify when your script will be injected and for
// which frames–all frames or the main frame only.
let scriptSource = "window.webkit.messageHandlers.test.postMessage(`Hello, world!`);"
let userScript = WKUserScript(source: scriptSource, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
userContentController.addUserScript(userScript)
// let config = WKWebViewConfiguration()
// config.userContentController = userContentController
// let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = self
webView.configuration.userContentController = userContentController
// Make sure in Info.plist you set `NSAllowsArbitraryLoads` to `YES` to load
// URLs with an HTTP connection. You can run a local server easily with services
// such as MAMP.
let htmlStr = "<html><body>Hello world - nojs</body></html>"
webView.loadHTMLString(htmlStr, baseURL: nil)
}
}
extension ViewController: WKScriptMessageHandler {
// Capture postMessage() calls inside loaded JavaScript from the webpage. Note that a Boolean
// will be parsed as a 0 for false and 1 for true in the message's body. See WebKit documentation:
// https://developer.apple.com/documentation/webkit/wkscriptmessage/1417901-body.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if let messageBody = message.body as? String {
print(messageBody)
}
}
}
另一件奇怪的事情是,我似乎无法创建一个简单的 WKWebView 应用程序来加载页面并显示它。这些都只是简单的测试,我的主要应用程序能够使用 AlamoFire/loadHTMLString() 显示页面 load/display 网页,我只是无法注入所需的 JS。
我在转换中所做的一切都非常简单,除了 userContentController 的分配外几乎不需要任何更改 - 也许这就是问题所在?这个例子在 iOS 中以他的原始样本为原型工作得很好。 https://github.com/rckim77/WKWebViewDemoApp/blob/master/WKWebViewDemoApp/ViewController.swift
我猜这里肯定缺少一些非常简单的东西。任何帮助将不胜感激!
下面是我如何在 Mac 上设置我的 WebView 试试这样的事情
import Cocoa
import WebKit
class ViewController: NSViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let javascript = """
function printStatement() {
try {
window.webkit.messageHandlers
.callbackHandler.postMessage({'payload': 'Hello World!'})
} catch(err) {
console.log('The native context does yet exist')
}
}
"""
let script = WKUserScript(
source: javascript,
injectionTime: WKUserScriptInjectionTime.atDocumentEnd,
forMainFrameOnly: true
)
webView.configuration.userContentController.add(
name: "callbackHandler"
)
webView.configuration.userContentController
.addUserScript(script)
webView.navigationDelegate = self
let html = """
<div onClick='javascript:printStatement()'>Print Statement</div>
"""
webView.loadHTMLString(html, nil)
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "callbackHandler") {
guard let body = message.body as? [String: Any] else {
print("could not convert message body to dictionary: \(message.body)")
return
}
guard let payload = body["payload"] as? String else {
print("Could not locate payload param in callback request")
return
}
print(payload)
}
}
}
希望这能回答您的问题并且有效,如果不告诉我,我会尽力解决!
嗯,事实证明问题的主要部分是我需要将 "App Sandbox" 和 "com.apple.security.files.user-selected.read-only" 的权利都设置为 WebTest.entitlements 中的 "no" =] 文件。
以前版本的 XCode 不是这种情况(我使用的是 V10.1),默认值基本上禁用了 WKWebView 我试图用它做的事情(即加载一个通过 URL 或 String)
的简单页面
然而,一旦我解决了这个问题,亚历克斯的修复确实有帮助......通过一些小的调整(必须将 'self' 添加到 userContentController.add() 函数。另外,我添加了我的 JS其最初目的是每次用户更改页面上的选择时 "push" 到 Swift。
这是我的最终代码:
import Cocoa
import WebKit
class ViewController: NSViewController, WKNavigationDelegate {
@IBOutlet var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let javascript = """
function printStatement() {
try {
var foo = window.getSelection().toString()
window.webkit.messageHandlers.callbackHandler.postMessage({'payload': foo})
} catch(err) {
console.log('The native context does yet exist')
}
}
function getSelectionAndSendMessage() {
try {
var currSelection = window.getSelection().toString()
window.webkit.messageHandlers.callbackHandler.postMessage({'payload': currSelection})
} catch(err) {
console.log('The native context does yet exist')
}
}
document.onmouseup = getSelectionAndSendMessage;
document.onkeyup = getSelectionAndSendMessage;
document.oncontextmenu = getSelectionAndSendMessage;
"""
let script = WKUserScript(
source: javascript,
injectionTime: WKUserScriptInjectionTime.atDocumentEnd,
forMainFrameOnly: true
)
webView.configuration.userContentController.add(self, name: "callbackHandler")
webView.configuration.userContentController.addUserScript(script)
webView.navigationDelegate = self
let html = """
<div onClick='javascript:printStatement()'>Print Statement</div>
This is some sample text to test select with
"""
webView.loadHTMLString(html, baseURL: nil)
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "callbackHandler") {
guard let body = message.body as? [String: Any] else {
print("could not convert message body to dictionary: \(message.body)")
return
}
guard let payload = body["payload"] as? String else {
print("Could not locate payload param in callback request")
return
}
print(payload)
}
}
}
感谢 Alex 的大力支持!
我正在使用一个 MacOS 应用程序,它需要使用 WKUserScript 功能将消息从网页发送回 MacOS 应用程序。我正在研究文章 https://medium.com/capital-one-tech/javascript-manipulation-on-ios-using-webkit-2b1115e7e405,它显示了这个在 iOS 中的工作并且工作得很好。
然而,我已经努力了几个星期,试图让它在我的 MacOS 中运行。这是我的代码示例,它符合要求并运行但没有成功打印在处理程序 userContentController()
中找到的消息import Cocoa
import WebKit
class ViewController: NSViewController, WKNavigationDelegate {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let userContentController = WKUserContentController()
// Add script message handlers that, when run, will make the function
// window.webkit.messageHandlers.test.postMessage() available in all frames.
userContentController.add(self, name: "test")
// Inject JavaScript into the webpage. You can specify when your script will be injected and for
// which frames–all frames or the main frame only.
let scriptSource = "window.webkit.messageHandlers.test.postMessage(`Hello, world!`);"
let userScript = WKUserScript(source: scriptSource, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
userContentController.addUserScript(userScript)
// let config = WKWebViewConfiguration()
// config.userContentController = userContentController
// let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = self
webView.configuration.userContentController = userContentController
// Make sure in Info.plist you set `NSAllowsArbitraryLoads` to `YES` to load
// URLs with an HTTP connection. You can run a local server easily with services
// such as MAMP.
let htmlStr = "<html><body>Hello world - nojs</body></html>"
webView.loadHTMLString(htmlStr, baseURL: nil)
}
}
extension ViewController: WKScriptMessageHandler {
// Capture postMessage() calls inside loaded JavaScript from the webpage. Note that a Boolean
// will be parsed as a 0 for false and 1 for true in the message's body. See WebKit documentation:
// https://developer.apple.com/documentation/webkit/wkscriptmessage/1417901-body.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if let messageBody = message.body as? String {
print(messageBody)
}
}
}
另一件奇怪的事情是,我似乎无法创建一个简单的 WKWebView 应用程序来加载页面并显示它。这些都只是简单的测试,我的主要应用程序能够使用 AlamoFire/loadHTMLString() 显示页面 load/display 网页,我只是无法注入所需的 JS。
我在转换中所做的一切都非常简单,除了 userContentController 的分配外几乎不需要任何更改 - 也许这就是问题所在?这个例子在 iOS 中以他的原始样本为原型工作得很好。 https://github.com/rckim77/WKWebViewDemoApp/blob/master/WKWebViewDemoApp/ViewController.swift
我猜这里肯定缺少一些非常简单的东西。任何帮助将不胜感激!
下面是我如何在 Mac 上设置我的 WebView 试试这样的事情
import Cocoa
import WebKit
class ViewController: NSViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let javascript = """
function printStatement() {
try {
window.webkit.messageHandlers
.callbackHandler.postMessage({'payload': 'Hello World!'})
} catch(err) {
console.log('The native context does yet exist')
}
}
"""
let script = WKUserScript(
source: javascript,
injectionTime: WKUserScriptInjectionTime.atDocumentEnd,
forMainFrameOnly: true
)
webView.configuration.userContentController.add(
name: "callbackHandler"
)
webView.configuration.userContentController
.addUserScript(script)
webView.navigationDelegate = self
let html = """
<div onClick='javascript:printStatement()'>Print Statement</div>
"""
webView.loadHTMLString(html, nil)
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "callbackHandler") {
guard let body = message.body as? [String: Any] else {
print("could not convert message body to dictionary: \(message.body)")
return
}
guard let payload = body["payload"] as? String else {
print("Could not locate payload param in callback request")
return
}
print(payload)
}
}
}
希望这能回答您的问题并且有效,如果不告诉我,我会尽力解决!
嗯,事实证明问题的主要部分是我需要将 "App Sandbox" 和 "com.apple.security.files.user-selected.read-only" 的权利都设置为 WebTest.entitlements 中的 "no" =] 文件。
以前版本的 XCode 不是这种情况(我使用的是 V10.1),默认值基本上禁用了 WKWebView 我试图用它做的事情(即加载一个通过 URL 或 String)
的简单页面然而,一旦我解决了这个问题,亚历克斯的修复确实有帮助......通过一些小的调整(必须将 'self' 添加到 userContentController.add() 函数。另外,我添加了我的 JS其最初目的是每次用户更改页面上的选择时 "push" 到 Swift。
这是我的最终代码:
import Cocoa
import WebKit
class ViewController: NSViewController, WKNavigationDelegate {
@IBOutlet var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let javascript = """
function printStatement() {
try {
var foo = window.getSelection().toString()
window.webkit.messageHandlers.callbackHandler.postMessage({'payload': foo})
} catch(err) {
console.log('The native context does yet exist')
}
}
function getSelectionAndSendMessage() {
try {
var currSelection = window.getSelection().toString()
window.webkit.messageHandlers.callbackHandler.postMessage({'payload': currSelection})
} catch(err) {
console.log('The native context does yet exist')
}
}
document.onmouseup = getSelectionAndSendMessage;
document.onkeyup = getSelectionAndSendMessage;
document.oncontextmenu = getSelectionAndSendMessage;
"""
let script = WKUserScript(
source: javascript,
injectionTime: WKUserScriptInjectionTime.atDocumentEnd,
forMainFrameOnly: true
)
webView.configuration.userContentController.add(self, name: "callbackHandler")
webView.configuration.userContentController.addUserScript(script)
webView.navigationDelegate = self
let html = """
<div onClick='javascript:printStatement()'>Print Statement</div>
This is some sample text to test select with
"""
webView.loadHTMLString(html, baseURL: nil)
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "callbackHandler") {
guard let body = message.body as? [String: Any] else {
print("could not convert message body to dictionary: \(message.body)")
return
}
guard let payload = body["payload"] as? String else {
print("Could not locate payload param in callback request")
return
}
print(payload)
}
}
}
感谢 Alex 的大力支持!