Render and Interact SVG with WKWebView

Trương Văn Kiên
4 min readMar 5, 2021

Hello everyone.

We recently finished a project name Seat selection. This project is about creating a module that allows users to select the seat directly on a map (like choosing a seat in the movie theater but in a bigger scale). So far, we applied for one stadium with 40.000 seats. Because that is a private project so I can not show anything about that.

When starting the project prototype, we know that iOS does not support SVG natively, so we experience some library to try to render and interact with SVG file, one of that is Macaw. Luckily, It works perfectly at that time, smooth and nothing to complain about that library. We did prototype successfully with the map include 3000 seats. But when trying with a 40.000 seat map, the performance becomes very bad. The map was rendered slowly, scutter responds when the user taping in. We trying to find another solution again.
In the end, we choose the way to load SVG directly into WKWebView and use Javascripts to handle the interaction of users.

Before dive into the technical, I will show the demo first. In this demo, we will draw random color into the path of SVG when users click on it, zoom in to the path, add a view in the center of the path.

Demo render and interact with SVG in WKWebView

Alright, Here we go

My favorite character :)))

First of all, the main idea is loading the SVG file into WKWebView and interact through Javascript so we need to create files index.html and main.js

index.html

One more thing you need to take over is marking the ID for the path you want to interact with. In this example, I will mark the ID for the path is path-{number}

We will use this SVG in this example

In main.jswe need to write a function to load the SVG file to body.

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

OK, It is enough to show SVG into web view now we back to iOS. Before we start you need to know something about WKWebView. Some component in WKWebView is:

  • WKWebView — allows web content to be loaded via a URL
  • WKScriptMessage — object created when a postMessage() is received
  • WKUserContentController — manages javascript posts and injection
  • WKScriptMessageHandler — protocol to access WKScriptMessage delegate methods
  • WKWebViewConfiguration — config passed to WKWebView

I think this post will too long if I explain all of them, so for more detail, you can read it here. In this example, We will use webview.evaluateJavaScript(anJavascriptFunction) to call Javascript function from iOS and webkit.messageHandlers.name.postMessage() to send data from Javascript back to iOS.

First of all, you need to init a 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
}()
}

Before we can interact with the SVG file through Javascript we need to prepare the configuration and pass it to WKWebView init function. Here we will listen to two handlers from Javascript is didTapPath and transferPathsInfo . We will work with these later in this post.

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) {}
}

And add it to the current view:

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

Next, we start loading the index.html onto 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))
}

And when the index.html file successfully load. We will call the loadSVG function we wrote before to load the SVG file. To know when it is finished loading we will conform the WKNavigationDelegate protocol, it has didFinish function.

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

Here we are using evaluateJavaScript method, it is the method allows us to call Javascript function. This method takes a String which should include the javascript function plus any arguments, therefore any arguments passed with the function have to be converted back to a String. Now build and run the project

Cool, it works.

I think this post is long enough, so we will continue to handle interaction in this post.

--

--