Я использую WKWebView внутри представления UIViewController для отображения веб-страницы, размещенной на сервере, с использованием конечной точки URL. На веб-странице используется Reactjs. Это вся информация о веб-странице, которая у меня есть. Код создает веб-представление и вставляет веб-представление как подпредставление представления контроллеров.
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)
Веб-страница загружается нормально, а также контроллер действует как делегат веб-просмотра и получает сообщения для него. Теперь я также реализую класс WKWebViewWrapper, который соответствует WKScriptMessageHandler. Затем этот класс может получать сообщения от объекта 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 (:, имя :) метод. Согласно документации, этот метод добавляет messageHandler параметра name к объекту webkit. Когда запускается обработчик сообщений, WKScriptMessageHandler's userContentController ( userContentController: WKUserContentController, didReceive message: WKScriptMessage) с полезными параметрами. Затем я вставляю javascript на веб-страницу, используя метод веб-просмотра evalJavaScript, который показан ниже
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. Этот сценарий выполняется правильно, так как я не получаю ошибки в блоке завершения метода оценитьJavaScript. Итак, теперь я могу быть уверен, что при возникновении события HTML кнопки onClick будет выполнена анонимная функция, которая, в свою очередь, отправит сообщение для messageHandler в объект webkit.
Теперь я вызываю метод setUpPlayerAndEventDelegation () WKWebViewWrapper из метода webView (_ webView: WKWebView, didFinish navigation: WKNavigation!), где я могу быть уверен, что все элементы HTML загружены WKWebViewDelegate, сравнивая объекты WKNavigation.
Поток выполняется, и после загрузки страницы, когда я нажимаю любую кнопку, мой обработчик сообщений сценария не наблюдает за событиями, то есть классом WKWebViewWrapper. Метод userContentController (_ userContentController: WKUserContentController, didReceive сообщение: WKScriptMessage) вообще не запускается.
Что-то мне здесь не хватает? Я плохо разбираюсь в Javascript. Пожалуйста, дайте мне знать, нужен ли Reactjs другой скрипт и прослушиватель событий для элементов кнопки. Я сослался на этот урок.
PS: если мы добавим аналогичный скрипт для вывода консольных сообщений в веб-браузере, который загрузил страницу, он будет работать нормально..
@RikeshSubedi Я пробовал оба события ранее, ни с одним из них не сработало
@ rohan-bhale Вы когда-нибудь находили решение этой проблемы? У меня такая же проблема. События кликов не запускаются.
@AbbeyJackson Я пока не нашел решения этой проблемы.
@AbbeyJackson Я обсуждал это со своими коллегами, у которых есть опыт разработки с использованием ReactJS. Я пробовал использовать большинство стандартных средств для регистрации слушателя событий несколькими способами. Это не работает.
@ Rohan-Bhale Итак, вчера в конце дня я заставил его работать. Я собираюсь опубликовать ответ, чтобы он был более заметен для других людей, ищущих, потому что для этой проблемы нет подходящих ответов.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Обратите внимание на важное (но менее известное) поведение WKWebViewConfiguration в Документы Apple,
WKWebViewConfiguration используется только при первой инициализации веб-представления. Вы не можете использовать этот класс для изменения конфигурации веб-представления после его создания.
Итак, обычно вам следует полностью настроить 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)
Я внес изменения как в сущности gist.github.com/rohanbproxce/7320c42cc96e1c6f72c2c58087c008c b. Изменения вносятся в метод viewDidLoad () контроллера представления, а также контроллер соответствует WKScriptMessageHandler. По-прежнему не получено никаких событий.
Для всех, кто читает этот ответ, это 100% правильная информация, однако есть большая проблема с исходным кодом плаката. Если у вас возникла та же проблема и вы выполнили правильную настройку, как описано в этом ответе (но он все еще не работает), пожалуйста, найдите мой ответ на этот вопрос ниже, в котором подробно описаны все ошибки javascript, которые я допустил (которые OP также сделал), из-за которых я не получал вызовы WKScriptMessage для событий щелчка: stackoverflow.com/a/53796125/5032318
@Ashok Большое спасибо, это действительно не было интуитивно для меня. Это кажется странным, поскольку вы не можете использовать раскадровку для веб-просмотра таким образом.
Так что я действительно заставил это работать. Было несколько проблем, но все они были связаны с ошибками в моем JavaScript. Однако JavaScript просто не выполнялся, а не выдавал никаких ошибок, из-за чего казалось, что это проблема iOS. Это заняло действительно много времени и много отладки через Safari.
По сути, то, что я обнаружил, вероятно, является ошибкой, которая широко распространена из-за того, что в Интернете мало документации / статей для отправки сообщений через WKMessagingScript. Все образцы показывают что-то вроде этого:
window.webkit.messageHandlers.test.postMessage(“message to post”)
Некоторые из них продолжают говорить, что вы можете отправить что угодно, даже словарь json. Они не могут сказать, что 1) вы НЕ можете передавать объект в эту функцию и 2) передача буквального словаря недопустима в JavaScript. Они также не приводят более подходящих примеров. Вы можете захотеть просто строковое сообщение, чтобы вы знали, что что-то произошло, но если вам нужно передать данные, вы получите их из элементов и, скорее всего, сделаете ошибку номер 1 в своей реализации (что и вы, и я сделали).
1) это важно. В вашем примере вы вызываете функцию. В JavaScript функции являются первоклассными гражданами, поэтому вы передаете объект. Вы также не можете, например, передать element.id, что я делал, потому что вам нужно передать элемент. Что вам нужно сделать, так это передать только значение типа фундамента.
*** Обратите внимание, что вы можете передать объект в JavaScript, такой как console.info(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 поддерживают его, и я бы не рекомендовал его использовать.
Однако я вижу две дополнительные проблемы с вашим кодом. Во-первых, вы используете onClick, когда вам нужно использовать click.
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
Если бы вы были веб-разработчиком, вы бы справились с обоими, но для iOS вы должны использовать только click.
Еще я заметил в вашем коде, что вы передаете eventName в функцию webkit window.webkit.messageHandlers.\(eventName).postMessage...... Я не совсем уверен, сработает это или нет. Я подозреваю, что это не так, потому что это не строка, это вызов функции. Хотя я вообще ничего не знаю о JavaScript (это был буквально первый JS, который я когда-либо писал), поэтому я могу ошибаться. В objc или swift вы не можете этого сделать при вызове функции. Даже если бы это сработало, я думаю, что это добавит слишком много сложности и не станет масштабируемым, если iOS WKMessagingScript был обновлен, чтобы больше этого не допускать. Я бы посоветовал использовать правильное имя. Если вы хотите инкапсулировать свой код, включите eventName.
спасибо за подробный ответ. Не могу дождаться, чтобы попробовать это. Код, которым я поделился, находился на полпути разработки, а потом я застрял. Теперь вы указали направление, в котором можно двигаться. Надеюсь, это сработает для меня.
Пожалуйста! Сообщите мне (и всем, кто читает!), Если вы найдете какие-либо дополнительные ошибки!
Это сработало. Я изменил onClick на click и заменил JSON.stringify (true) на String (JSON.stringify (true)), и это сработало. Мне потребовалось некоторое время, чтобы проверить это решение, поскольку мы перешли на нативную реализацию, отказавшись от реализации на основе веб-просмотра. Мне пришлось создать еще один клон и вернуться к фиксации, в которой у меня был этот код, и проверить ваше решение. Это работает, и я приму этот исчерпывающий ответ.
О, это здорово! Я рад, что мы смогли разобраться в этом, потому что я видел много людей с этой проблемой на различных сайтах и досках проблем GitHub! Надеюсь, они приземлятся здесь и увидят это, прежде чем проведут столько же дней, сколько мы с вами!
попробуйте добавить событие click, а не onClick, с помощью метода addEventListener.