ylliX - Online Advertising Network

How to use JSContext from JavaScriptCore to create two way communication with web page

What is “JavaScriptCore”? Well, it is a name for internal Safari javascript engine. Is it useful? For most of us it is not, but if you thinking about communicating between your Swift app and web page, it can be really useful.

The problem is simple if you just want to fire some actions if button or link is clicked. Just add “#myaction” in url, then you can easily capture URL change. More complex question is – how web page can fire such action in the way which can both iOS and Android use? On Android URL change event works different so that solution is useless. But those guys can capture native js calls like:

WebBridge.myAction("argument");

Can we (iOS guys) also capture such calls? Not without JavaScriptCore and its JSContext.

Lets start with creating UIWebView, UITextField and connecting them to proper methods. If you don’t have time for this, feel free to grab demo project from github.

After initial work your code should be like this:

class ViewController: UIViewController {
    @IBOutlet weak var webview: UIWebView!
    @IBOutlet weak var textfield: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func textfieldChanged(_ sender: UITextField) {
        
    }

}

Next, we need to add support for UIWebViewDelegate and use webViewDidFinishLoad method, inside which we will create our JSContext. Also don’t forget about importing JavaScriptCore module and load our sample page.

class ViewController: UIViewController, UIWebViewDelegate {
    @IBOutlet weak var webview: UIWebView!
    @IBOutlet weak var textfield: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        webview.delegate = self
        let url = Bundle.main.url(forResource: "web", withExtension: "html")
        webview.loadRequest(URLRequest(url: url!))
    }

    @IBAction func textfieldChanged(_ sender: UITextField) {
        
    }

    func webViewDidFinishLoad(_ webView: UIWebView) {
        let ctx = webview.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as! JSContext

    }
}

What happens here? In viewDidLoad method nothing special, we’re just loading our web.html page (included on github) into webview. More happens inside webViewDidFinishLoad – here we are creating our JSContext from special javascript object available only on Safari.

Next part is our communication class and protocol. You have to extend JSExport protocol, create your methods and then create your handler class implementing that protocol. Here goes example:

@objc protocol CommunicationProtocol: JSExport {
    static func callNativeFunction(_ mytext: String)
}

@objc class CommunicationClass: NSObject, CommunicationProtocol {
    class func callNativeFunction(_ mytext: String) {
        print("Native function called \(mytext)")
    }
}

Now important part, in our webViewDidFinishLoad method, add:

ctx.setObject(unsafeBitCast(CommunicationClass.self, to: AnyObject.self), forKeyedSubscript: "SwiftBridge" as (NSCopying & NSObjectProtocol)!)

This is key to everything. On our JS context we are injecting our CommunicationClass available as “SwiftBridge” in javascript. This will allow webpage to call:

SwiftBridge.callNativeFunction('hi from web!');

Of course you can name your class whatever you like. Now just run your project, and when you click link in webview, you should see in XCode console something like:
Native function called hi from web!

But wait, there is more! You can also inject blocks instead of object if you want:

let textChanged: @convention(block) (String) -> () =
            { newtext in
                print("text changed \(newtext)")
        }
        ctx.setObject(unsafeBitCast(textChanged, to: AnyObject.self), forKeyedSubscript: "textChanged" as (NSCopying & NSObjectProtocol)!)

If you find this usefull, just spread the word. Share on social. On github you can see also how to call JS functions from Swift, you can enter some text in web text input, and native textfield will reflect changes, also you can enter text in native control and web will show same.

Note: Project was created on XCode 8.2.1 and Swift 3.0.
Github: https://github.com/blastar/JavaScriptCore-JSContext

Leave a Reply