Взаимодействие Swift с JavaScript
Во все приложения для операционной системы iOS встроена поддержка веб-компонентов. Это нужно, преимущественно, для отображения каких-либо веб-страниц. Однако, для взаимодействия с веб-содержимым в приложениях - необходим некий "мост". От технологии Swift к технологии JavaScript. При разработке своего приложения для iOS я столкнулся с данной проблемой. В моем приложении существует WKWebView, который запускает фреймворк, написанный на JavaScript. Этот фреймворк в свою очередь берет данные для вычислений из базы данных SQLite нативного приложения, производит подсчеты, возвращает новые данные в Swift. Затем эти данные записывает обратно в базу данных. Чуть сложновато. Но речь не об этом.
К счастью компонент WKWebView оснащен таким "мостом" для взаимодействия между Swift и JavaScript. В данной статье разберем (с примером) его работу.
Ключевые компоненты
- WKWebView - позволяет загрузить веб-контент по URL
- WKScriptMessage - объект, который создается после выполнения метода postMessage()
- WKUserContentController - менеджер отправки и получения JavaScript
- WKScriptMessageHandler - протокол для доступа к методам WKScriptMessage
- WKWebViewConfiguration - настройки, передаваемые в WKWebView
Краткий обзор
Представленная выше диаграмма отражает путь от и до web-view. Термин "doStuff" общий, пожалуйста, подключите ваше воображение для замены его поведением, которое вам нужно 😉
На приведенной выше диаграмме видно, что при правильно написанной программе взаимодействие начинается с компонента WebView. Первым делом запускается программа, написанная на языке Swift. Внутри программы срабатывают сценарии открытия компонента WebView. Данный компонент инициализирует загрузку в отдельном процессе веб-документа html. Затем html документ выполняет вложенный код JavaScript.
Вызов триггера postMessage
После того, как веб-документ загружен, весь код JavaScript обработан - начинается самое интересное. JavaScript вызывает триггер-метод postMessage в обработчике событий doStuffMessageHandler. ( ➡ Позже я покажу как добавить обработчик в коде Swift, который принимает имя doStuffMessageHandler'а. ) Тут важный момент 💡 Название обработчика событий (MessageHandler) может быть абсолютно любым. На ваше усмотрение. Например: turnOnMusic.postMessage(trackName). Важно запомнить, в коде Swift вам нужно будет обработать совпадение только с именем, которое идет до .postMessage(). Оно называется "messageName", а параметры, которые передаются в скобках postMessage(parameters) - "messageBody".
Теперь, давайте на примере проверим работоспособность нашей теории. Создадим новый пустой проект Xcode, и добавим в него пустой файл с именем index.html. Откроем этот файл в редакторе Xcode и пропишем следующее:
<html> <script> window.webkit.messageHandlers.doStuffMessageHandler.postMessage({ param1: "stuff", param2: "1000" }); </script> </html>
Чтобы этот код запустился, необходимо сначала загрузить веб-страницу в Swift:
if let url = Bundle.main.url(forResource: "index", withExtension: "html") { let request = URLRequest(url: url) webView.load(request) }
В данном примере из JavaScript в Swift передаются два параметра, но вы можете добавлять столько параметров, сколько вам необходимо. JavaScript код можно добавлять несколькими способами. Можно встроить его непосредственно в html страницу, можно указать в html ссылку на .js файл со скриптом. Так же можно инжектировать скрипт с помощью WKUserScripts и метода addUserScript() в userContentController'е. Такого рода скрипты могут выполняться в момент, когда страница загружается или когда она завершила свою загрузку.
"Слушатели" метода postMessage() в iOs приложении
Для того, чтобы JavaScript мог обработать message handler, нужно чтобы была передана его конфигурация в WKWebView во время инициализации программы. Это делается с помощью вызова add() метода из userContentController свойства объекта WKWebViewConfiguration передающего имя handler'а. Класс, принимающий сообщения, также должен быть передан в метод add() как параметр. Рассмотрим пример:
let doStuffMessageHandler = "doStuffMessageHandler" let configuration = WKWebViewConfiguration() configuration.userContentController.add(self, name: doStuffMessageHandler) let rect = CGRect(x:0, y:0, width: frame.width, height: frame.height) let webView = WKWebView(frame: rect, configuration: configuraion)
Класс, передаваемый в add() метод должен соответствовать протоколу WKScriptMessageHandler. Это свойство разрешит нашему "целевому" классу делегировать методы, которые выполняются в следствии обработки определенного javascript handler'а. В следующем листинге мы расширяем наш класс WebViewController для удовлетворения протокола и добавляем метод didReceiveMessage для обработки событий:
extension WebViewController: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if message.name == doStuffMessageHandler { guard let dict = message.body as? [String: AnyObject], let param1 = dict["param1"] as? String, let param2 = dict["param2"] as? Int else { return } // doStuff(with: param1, param2) } } }
Когда срабатывает postMessage триггер, делегирующий метод уведомляет приложение с помощью WKScriptMessage объекта. WKScriptMessage содержит "name", одинаковое с handler'ом, и "body", включающее в себя любые параметры или текст. Эти параметры посылаются из JavaScript в Swift. Для обработки сообщения мы должны поймать имя handler'а в Swift, и принять его параметры.
Обратная связь с web-view
Если приложение получило и обработало какие-то данные из JavaScript, почти наверняка оно должно вернуть результат этой обработки. Для обратного сообщения Swift с JavaScript существует метод evaluateJavascript. Этот метод принимает строку типа String, как параметр. Эта строка представляет собой некий код JavaScript или вызов JS функции. В качестве второго параметра идет блок, который срабатывает после отправки сообщения. Он также может вернуть ошибку, возникающую при отправки сообщения.
func doStuffDidFinish(result: DoStuffResult, param1: String, param2: String) { let data: [String: String] = [ "param1": "\(param1)", "param2": "\(param2)" ] guard let json = try? JSONEncoder().encode(data), let jsonString = String(data: json, encoding: .utf8) else { return } switch result { case .success: let javascript = "window.actions.suffWasSuccessful('\(jsonString)')" webView.evaluateJavaScript(javascript, completionHandler: nil) case .failure: let javascript = "window.actions.stuffFailed('\(jsonString)')" webView.evaluateJavaScript(javascript, completionHandler: nil) } }
В коде выше мы передаем данные в формате JSON как строку.
Скрипт который мы "евалуируем", должен находиться на веб-странице, загруженной в web-view во время его запуска. Если это не так, вы можете посмотреть, как внедрить его с помощью метода addUserScript () в userContentController. Это финальный шаг сообщения приложения с веб-представлением.
В данной статье описана самая суть(ядро) взаимодействия Swift и JavaScript. Тут опущены некоторые подробности, отсутствие которых может сбить с толку новичков в Swift программировании. Дабы избежать замешательства, в ближайшее время я создам учебный проект в Xcode, в котором описанные выше методики можно будет изучить наглядно. И может быть запишу процесс создания этого проекта на видео. Большое спасибо за внимание, надеюсь эти материалы оказались для вас полезны.