Skip to content

Instantly share code, notes, and snippets.

@wayne5540
Last active April 29, 2021 14:35
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save wayne5540/f013a18e00bde8d5da3a01d65cd88536 to your computer and use it in GitHub Desktop.
Save wayne5540/f013a18e00bde8d5da3a01d65cd88536 to your computer and use it in GitHub Desktop.
This article is to show how to inject JavaScript into iframs under iOS web view and so we can communicate with it.

[iOS - Swift] How to communicate with iFrames inside WebView

To provide better shopping experience for Onefill users, we want to support as many shopping site as we can by injecting our JavaScript engine into those sites. However, some of them are using iframe which is outdated HTML tag to implement some forms like payment and signup. And due to security issue JavaScript can’t communicate with iframe unless it’s same domain or it’s your domain. So here is the approach we did to support iframe under iOS web view and so we can communicate with it.

  • Xcode: Version 8.2.1 (8C1002)
  • Swift: Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)

Conclusion first

  1. Use WKWebView instead UIWebView or WebView for 2 reasons:
  2. Use built-in API WKUserScript inject JS, set forMainFrameOnly as false to inject JS into every frames (including iframe)
  3. To communicate between iframe and app, use JS webkit API

Code

inject JS into iframe

Use WKUserScript inject JavaScript into all frames Example: Inject a script to make every h1 tag red.

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate {
    var webView: WKWebView!

    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        let contentController = WKUserContentController()
        let js: String = "var h1s = document.querySelectorAll('h1'); for (var i = 0; i < h1s.length; i++) { h1s[i].style.color = 'red' };"
        let userScript = WKUserScript(source: js, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: false)
        contentController.addUserScript(userScript)
        webConfiguration.userContentController = contentController

        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        webView.navigationDelegate = self
        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let myURL = URL(string: "http://localhost:3000")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }
}

Communicate with iFrame

Use JavaScript webkit API Almost same as “inject JS into iframe”, however, we need to implement WKScriptMessageHandler protocol to receive message sent by JavaScript.

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
    var webView: WKWebView!

    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        let contentController = WKUserContentController()
        // Inject JavaScript which sending message to App
        let js: String = "window.webkit.messageHandlers.callbackHandler.postMessage('Hello from JavaScript');"
        let userScript = WKUserScript(source: js, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: false)
        contentController.removeAllUserScripts()
        contentController.addUserScript(userScript)
        // Add ScriptMessageHandler
        contentController.add(
            self,
            name: "callbackHandler"
        )

        webConfiguration.userContentController = contentController

        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        webView.navigationDelegate = self
        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let myURL = URL(string: "http://localhost:3000")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }

    // Implement `WKScriptMessageHandler`,handle message which been sent by JavaScript
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if(message.name == "callbackHandler") {
            print("JavaScript is sending a message \(message.body)")
        }
    }
}

References

  1. wkwebview-and-javascript-in-ios-8-using-swift
  2. WKWebit
  3. 2014 WWDC about WKWebView
  4. Apple API Docs:
@rafaelkapela
Copy link

I received this error: Argument type 'webView' does not conform to expected type 'WKScriptMessageHandler'

@wjmelements
Copy link

@rafaelkapela fix the typo in func userContentController and it will conform

@tosifkanuga
Copy link

I am unable to load the js

<script type='text/javascript' src='https://stackoverflow.com/questions/41996624/inject-javascript-into-webview-swift'></script> <script type='text/javascript'>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment