Вызовите функцию JavaScript из собственного кода в WKWebView
Используя WKWebView в iOS 8, как я могу запустить функцию JavaScript с родной стороны или иным образом обмениваться данными с родной стороны на JavaScript? Не существует метода, подобного UIWebView stringByEvaluatingJavaScriptFromString:
.
(Я могу использовать - addScriptMessageHandler:name:
для объекта configuration.userContentController
, чтобы разрешить связь с JS на native, но я ищу противоположное направление.)
Ответы
Ответ 1
(Я подал Радар для этого вскоре после того, как задал вопрос здесь.)
Новый метод был добавлен несколько дней назад (спасибо jcesarmobile за указание на это):
Добавить -[WKWebView evaluateJavaScript:completionHandler:]
http://trac.webkit.org/changeset/169765
Метод доступен в iOS 8 beta 3 и выше. Вот новая подпись метода:
/* @abstract Evaluates the given JavaScript string.
@param javaScriptString The JavaScript string to evaluate.
@param completionHandler A block to invoke when script evaluation completes
or fails.
@discussion The completionHandler is passed the result of the script evaluation
or an error.
*/
- (void)evaluateJavaScript:(NSString *)javaScriptString
completionHandler:(void (^)(id, NSError *))completionHandler;
Документы доступны здесь: https://developer.apple.com/documentation/webkit/wkwebview/1415017-evaluatejavascript.
Ответ 2
подробности
- Xcode 9.1, Swift 4
- Xcode 10.2 (10E125), Swift 5
Описание
Скрипт вставляется в страницу, которая будет отображаться в WKWebView. Этот скрипт вернет URL страницы (но вы можете написать другой код JavaScript). Это означает, что событие сценария генерируется на веб-странице, но оно будет обрабатываться в нашей функции:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {...}
Решение
extension WKUserScript {
enum Defined: String {
case getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
case getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"
var name: String { return rawValue }
private var injectionTime: WKUserScriptInjectionTime {
switch self {
case .getUrlAtDocumentStartScript: return .atDocumentStart
case .getUrlAtDocumentEndScript: return .atDocumentEnd
}
}
private var forMainFrameOnly: Bool {
switch self {
case .getUrlAtDocumentStartScript: return false
case .getUrlAtDocumentEndScript: return false
}
}
private var source: String {
switch self {
case .getUrlAtDocumentEndScript, .getUrlAtDocumentStartScript:
return "webkit.messageHandlers.\(name).postMessage(document.URL)"
}
}
fileprivate func create() -> WKUserScript {
return WKUserScript(source: source,
injectionTime: injectionTime,
forMainFrameOnly: forMainFrameOnly)
}
}
}
extension WKWebViewConfiguration {
func add(script: WKUserScript.Defined, scriptMessageHandler: WKScriptMessageHandler) {
userContentController.addUserScript(script.create())
userContentController.add(scriptMessageHandler, name: script.name)
}
}
использование
Init WKWebView
let config = WKWebViewConfiguration()
config.add(script: .getUrlAtDocumentStartScript, scriptMessageHandler: self)
config.add(script: .getUrlAtDocumentEndScript, scriptMessageHandler: self)
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
Поймать события
extension ViewController: WKScriptMessageHandler {
func userContentController (_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if let script = WKUserScript.Defined(rawValue: message.name),
let url = message.webView?.url {
switch script {
case .getUrlAtDocumentStartScript: print("start: \(url)")
case .getUrlAtDocumentEndScript: print("end: \(url)")
}
}
}
}
Пример полного кода
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
private var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.add(script: .getUrlAtDocumentStartScript, scriptMessageHandler: self)
config.add(script: .getUrlAtDocumentEndScript, scriptMessageHandler: self)
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
webView.load(urlString: "http://apple.com")
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController (_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if let script = WKUserScript.Defined(rawValue: message.name),
let url = message.webView?.url {
switch script {
case .getUrlAtDocumentStartScript: print("start: \(url)")
case .getUrlAtDocumentEndScript: print("end: \(url)")
}
}
}
}
extension WKWebView {
func load(urlString: String) {
if let url = URL(string: urlString) {
load(URLRequest(url: url))
}
}
}
extension WKUserScript {
enum Defined: String {
case getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
case getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"
var name: String { return rawValue }
private var injectionTime: WKUserScriptInjectionTime {
switch self {
case .getUrlAtDocumentStartScript: return .atDocumentStart
case .getUrlAtDocumentEndScript: return .atDocumentEnd
}
}
private var forMainFrameOnly: Bool {
switch self {
case .getUrlAtDocumentStartScript: return false
case .getUrlAtDocumentEndScript: return false
}
}
private var source: String {
switch self {
case .getUrlAtDocumentEndScript, .getUrlAtDocumentStartScript:
return "webkit.messageHandlers.\(name).postMessage(document.URL)"
}
}
fileprivate func create() -> WKUserScript {
return WKUserScript(source: source,
injectionTime: injectionTime,
forMainFrameOnly: forMainFrameOnly)
}
}
}
extension WKWebViewConfiguration {
func add(script: WKUserScript.Defined, scriptMessageHandler: WKScriptMessageHandler) {
userContentController.addUserScript(script.create())
userContentController.add(scriptMessageHandler, name: script.name)
}
}
Info.plist
добавить в настройках безопасности транспорта Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Результат
![enter image description here]()
Ресурсы
Свойства и методы объекта документа
Ответ 3
Это может быть не идеальный метод, но в зависимости от вашего прецедента вы можете просто перезагрузить WKWebView после заражения пользователя script:
NSString *scriptSource = @"alert('WKWebView JS Call!')";
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:scriptSource
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:YES];
[wkWebView.configuration.userContentController addUserScript:userScript];
[wkWebView reload];
Ответ 4
Здесь что-то, что работает для меня:
Создайте расширение на WKWebView, которое определяет метод runJavaScriptInMainFrame:. В методе расширения используйте метод NSInvocationOperation для вызова недокументированного метода _runJavaScriptInMainFrame:.
extension WKWebView {
func runJavaScriptInMainFrame(#scriptString: NSString) -> Void {
let selector : Selector = "_runJavaScriptInMainFrame:"
let invocation = NSInvocationOperation(target: self, selector: selector, object: scriptString)
NSOperationQueue.mainQueue().addOperation(invocation)
}
}
Чтобы использовать, звоните:
webview.runJavacriptInMainFrame:(scriptString: "some javascript code")
Благодаря Larsaronen для предоставления ссылки на частный API для WKWebView.
Ответ 5
Я только начал копаться вокруг API WKWebView, так что это может быть не самый лучший способ, но я думаю, вы могли бы сделать это со следующим кодом:
NSString *scriptSource = @"console.log('Hi this is in JavaScript');";
WKUserScript *userScript = [[WKUserScript alloc]
initWithSource:scriptSource
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:YES];
[myWKController addUserScript:userScript];
(из выступления WWDC'14)
Ответ 6
Ходят слухи, что это ошибка, потому что есть частная функция, подобная (?) для общедоступной в UIWebView для оценки javascript из obj-C.