Embedding web into native apps is a frequent approach to quickly add content into app. It can about support access, a contact form but also for more complex approach to bootstrap a missing feature. Webkit framework allows you to so and couple it with Javascript to improve the user experience.

Historically, iOS allows to use embedding web into UIWebView. It’s handy as a first approach, handling cookies on it’s own but quite limited on the Javascript side, mostly because of its missing core engine.

Here comes Webkit, a framework allowing developers to use same Javascript engine that powers Safari in their mobile apps.

iOS Webkit Framework

Prerequisites

To test our iOS app with some Javascript content, I’ll use an html file with a very basic form in it and two javascript functions: * A login action, forwarding the data form to the native app * A method to change the title of the page

It could look like those following

function sendLoginAction() {
   try {
       webkit.messageHandlers.loginAction.postMessage(
           document.getElementById("email").value + " " + document.getElementById("password").value
       );
   } catch(err) {
       console.log('The native context does not exist yet');
   }
}

function mobileHeader() {
   document.querySelector('h1').innerHTML = "WKWebView Mobile";
}

iOS to Javascript

WKWebView can be initialised with a custom configuration, including user agent used, the data store to be use by web views but also user content controller to eventually associate with the web view itself. This is the one we are going to use to send a call from iOS to Javascript.

First we need to create this user content controller and create the user script we want to handle.

let contentController = WKUserContentController()
let userScript = WKUserScript(
   source: "mobileHeader()",
   injectionTime: WKUserScriptInjectionTime.atDocumentEnd,
   forMainFrameOnly: true
)
contentController.addUserScript(userScript)

This code will inject the source code, as soon at the document has finished loading. It will actually execute mobileHeader and update the h1 title from the Javascript method seen above.

Then we’ll instantiate the web configuration with that controller and finally gives it to the WKWebView itself

let config = WKWebViewConfiguration()
config.userContentController = contentController
self.webView = WKWebView(frame: self.view.bounds, configuration: config)

Finally, after adding my web view to the main one, here is my result, the first one is loaded from Safari.

iOS Webkit JavaScript

The second one comes from the iOS simulator.

iOS Webkit Simulator

So we can see how to add javascript to the web view from iOS and how to execute it. Now let’s see from Javascript to iOS

Javascript to iOS

On this side, Webkit is well organised, there is already a protocol to handle that on it’s own: WKScriptMessageHandler

From the Apple documentation:

A class conforming to the WKScriptMessageHandler protocol provides a method for receiving messages from JavaScript running in a webpage.

Exactly what we need.

Once added to your class, we’re going to implement it’s userContentController to receive the content send from Javascript, limiting only to the loginAction we want for now.

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {       
       if message.name == "loginAction" {
           print("JavaScript is sending a message \(message.body)")
       }
   }

Finally, we are going to add a listener to this event into our WKUserContentController previously created

contentController.add(self, name: "loginAction”)

Running again on my iOS simulator, after filling the form, I can finally catch the values sent back.

iOS Webkit Callback

At the end we managed to forward event from iOS to Javascript and Javascript back to iOS too. However, there is way more to discover around Webkit and it’s navigation, how to handle loading and cookies in it that I’ll cover in another blog post.

During this team, here is the WebKit sample project based on Xcode 9.1 and Swift4, I also included the web pages used as resources next to it.