Всем привет.

Недавно мы закончили проект под названием Seat selection. Этот проект посвящен созданию модуля, позволяющего пользователям выбирать место прямо на карте (как выбор места в кинотеатре, но в большем масштабе). Пока что мы подали заявку на один стадион на 40 000 мест. Поскольку это частный проект, я не могу ничего показать по этому поводу.

При запуске прототипа проекта мы знаем, что iOS изначально не поддерживает SVG, поэтому мы используем некоторую библиотеку, чтобы попытаться отобразить и взаимодействовать с файлом SVG, одна из которых — Macaw. К счастью, в то время она работала отлично, плавно и не на что жаловаться на эту библиотеку. Мы успешно сделали прототип с картой на 3000 мест. Но при попытке с картой на 40 000 мест производительность становится очень плохой. Карта отрисовывалась медленно, скаттер реагирует, когда пользователь нажимает на нее. Мы снова пытаемся найти другое решение.
В конце концов, мы выбираем способ загрузки SVG непосредственно в WKWebView и используем Javascripts для обработки взаимодействия пользователей.

Прежде чем погрузиться в технические подробности, я сначала покажу демо. В этой демонстрации мы будем рисовать случайный цвет на пути SVG, когда пользователи нажимают на него, увеличиваем масштаб пути, добавляем представление в центре пути.

Хорошо, поехали

Прежде всего, основная идея заключается в загрузке файла SVG в WKWebView и взаимодействии через Javascript, поэтому нам нужно создать файлы index.html и main.js.

Еще одна вещь, которую вам нужно взять на себя, — это пометить идентификатор пути, с которым вы хотите взаимодействовать. В этом примере я отмечу идентификатор пути path-{number}.

В main.js нам нужно написать функцию для загрузки файла SVG в body.

function loadSVG(rawSVG) {
    document.getElementById("body").innerHTML = rawSVG
}

Хорошо, достаточно показать SVG в веб-представлении, теперь мы возвращаемся к iOS. Прежде чем мы начнем, вам нужно кое-что узнать о WKWebView. Некоторый компонент в WKWebView:

  • WKWebView — позволяет загружать веб-контент через URL-адрес.
  • WKScriptMessage — объект, созданный при получении postMessage()
  • WKUserContentController — управляет публикациями и внедрением javascript.
  • WKScriptMessageHandler — протокол для доступа к методам делегата WKScriptMessage.
  • WKWebViewConfiguration — конфигурация передается в WKWebView.

Я думаю, что этот пост будет слишком длинным, если я объясню их все, поэтому более подробно вы можете прочитать здесь. В этом примере мы будем использовать webview.evaluateJavaScript(anJavascriptFunction) для вызова функции Javascript из iOS и webkit.messageHandlers.name.postMessage() для отправки данных из Javascript обратно в iOS.

Прежде всего, вам нужно инициировать WKWebView

class SVGWebView: UIView {
    private(set) lazy var webView: WKWebView = {
        let configuration = self.getWebViewConfig()
        let webView = WKWebView(frame: CGRect(origin: .zero, size: UIScreen.main.bounds.size), configuration: configuration)
        webView.contentMode = .scaleAspectFit
        webView.navigationDelegate = self
        webView.scrollView.showsHorizontalScrollIndicator = false
        webView.scrollView.showsVerticalScrollIndicator = false
        webView.translatesAutoresizingMaskIntoConstraints = false
        return webView
    }()
}

Прежде чем мы сможем взаимодействовать с файлом SVG через Javascript, нам нужно подготовить конфигурацию и передать ее функции инициализации WKWebView. Здесь мы послушаем два обработчика из Javascript — didTapPath и transferPathsInfo. Мы будем работать с ними позже в этом посте.

func getWebViewConfig() -> WKWebViewConfiguration {
    let config = WKWebViewConfiguration()
    let pref = WKPreferences()
    config.preferences = pref
    //listen when user tap a path
    config.userContentController.add(self, name: "didTapPath")
    //send all paths to iOS to show in the tableview bellow the view
    config.userContentController.add(self, name: "transferPathsInfo")
    return config
}
extension SVGWebView: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {}
}

И добавьте его в текущий вид:

override init(frame: CGRect) {
    super.init(frame: frame)
    self.addSubview(webView)
}
required init?(coder: NSCoder) {
    super.init(coder: coder)
    self.addSubview(webView)
}

Далее мы начинаем загружать index.html в WKWebView.

func loadWebView() {
    let bundle = Bundle.main
    guard let path = bundle.path(forResource: "index", ofType: "html") else { return }
    let url = URL(fileURLWithPath: String(format: "%@", path))
    webView.load(URLRequest(url: url))
}

И когда файл index.html успешно загрузится. Мы вызовем функцию loadSVG, которую мы написали ранее, чтобы загрузить файл SVG. Чтобы узнать, когда загрузка будет завершена, мы будем соответствовать протоколу WKNavigationDelegate, у него есть функция didFinish.

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    loadSVG(svgName: "princess")
}
func loadSVG(svgName: String) { 
    let rawSVG = readSVGFromFile()
    let loadSVG = "loadSVG('\(rawSVG)')"
    webView.evaluateJavaScript(loadSVG)
}

Здесь мы используем метод evaluateJavaScript, этот метод позволяет нам вызывать функцию Javascript. Этот метод принимает строку, которая должна включать функцию javascript и любые аргументы, поэтому любые аргументы, переданные функцией, должны быть преобразованы обратно в строку. Теперь создайте и запустите проект

Круто, работает.

Я думаю, что этот пост достаточно длинный, поэтому мы продолжим взаимодействие в этом посте.