You are currently viewing Injecting JavaScript Into Web View In iOS

Injecting JavaScript Into Web View In iOS

Using a web view to display some frequently updated content such as FAQ is a very common tactic used by most iOS developers. By doing so, developers can easily update the web view content without going through the App Store review process. On the other hand, users will be able to view the online content without having to leave the app.

Some very common problems we can expect when using such tactics are the styling of the web view content does not match with the design of the app, or the content does not scale correctly in a mobile device.

In this article, let’s tackle both of these problems by using a technique called “JavaScript Injection“. Here’s a sneak peek of what you will learn in this article:

Styling HTML in web view using JavaScript Injection in iOS
Styling HTML in web view using JavaScript Injection

Look interesting? Let’s get into it!


Preparing CSS and JavaScript for Injection

For this article, we will be styling this HTML that I prepared earlier on. In order to style the HTML, we must first create the CSS that defines the styling that we want.

let css = """
html, body {
  overflow-x: hidden;
}

body {
  background-color: #333333;
  line-height: 1.5;
  color: white;
  padding: 10;
  font-weight: 600;
  font-family: -apple-system;
}
"""

Assuming that you have some basic CSS knowledge, the code above is pretty self-explanatory. However, I do want to draw your attention to the font-family property. Notice how I set it as -apple-system so that the web view will render the HTML using the iOS system font.

Pro Tip:

If your styling requires long and complicated CSS, it is recommended to read the CSS from a CSS file.

Now, let’s write some JavaScript that loads the CSS we just wrote.

// Make CSS into single liner
let cssString = css.components(separatedBy: .newlines).joined()

// Create JavaScript that loads the CSS
let javaScript = """
   var element = document.createElement('style');
   element.innerHTML = '\(cssString)';
   document.head.appendChild(element);
"""

Do note that it is important to make the CSS into a single liner before combining it with the JavaScript, or else the injection will not work.


The JavaScript Injection

This is where the magic happens! We will use the WKUserScript class to inject JavaScript into our web view.

// Create user script that inject the JavaScript after the HTML finishes loading
let userScript = WKUserScript(source: javaScript,
                              injectionTime: .atDocumentEnd,
                              forMainFrameOnly: true)

As you can see, we init the WKUserScript instance using the JavaScript we just created and ask it to inject the JavaScript after the HTML document finishes loading (using .atDocumentEnd). This is very important as the JavaScript won’t work if the injection happens before that.

With the user script in place, we can proceed to load the user script into the web view like so:

// Set user script to a configuration object and load it into the webView
let userContentController = WKUserContentController()
userContentController.addUserScript(userScript)
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
let webView = WKWebView(frame: CGRect.zero, configuration: configuration)

Scaling the HTML Content

Now, if we go ahead and load the HTML that I prepared earlier on,

// Load content to webView
DispatchQueue.global().async {
    
    let link = "https://swiftsenpai.com/wp-content/uploads/2022/01/plainHtmlSample.html"
    guard
        let url = URL(string: link),
        let htmlContent = try? String(contentsOf: url, encoding: .utf8) else {
        assertionFailure("Fail to get agreement HTML")
        return
    }
    
    DispatchQueue.main.async {
        // Load HTML string to web view in main thread
        webView.loadHTMLString(htmlContent, baseURL: nil)
    }
}

we will see something like this:

HTML scale wrongly in iOS web view
HTML scale wrongly in iOS web view

The reason this is happening is that our HTML is missing the <meta> tag. Therefore, the web view has no idea how to scale the HTML correctly. The solution to this is pretty straightforward, all we need to do is prepend the following <meta> viewport element to the HTML.

<meta name=\"viewport\" content=\"user-scalable=no, width=device-width\">

With that in mind, we can now update our sample code like so:

// Prepend `<meta>` viewport element to the HTML so that it will scale correctly in a mobile device
let metaTag = "<meta name=\"viewport\" content=\"user-scalable=no, width=device-width\">"
let html = "\(metaTag)\(htmlContent)"

// Load HTML string to web view in main thread
webView.loadHTMLString(html, baseURL: nil)

That’s it! If you try to execute the sample code, you should be able to see the HTML being nicely rendered in the web view.

Feel free to get the full sample code here and give it a try.


Wrapping Up

Styling HTML is just one of the use cases of JavaScript injection. What other use cases can you think of? Feel free to share your thoughts with me on Twitter, I would really like to hear from you!

Thanks for reading. 👨🏻‍💻


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.