Sending data between Objective-C & JavaScript

Whether you’re building a hybrid or native app, there are times when you may need to embed a web view. At these times, you’ll also likely need to pass data between the native and web layers. Most of the time, I’d use Cordova and let it handle this, however there are times when this isn’t appropriate and in such cases knowing how to achieve this in a robust way is important. I came up against one of these occasions on a project recently and thought it would be useful to share the solution. It is worth noting that my usecase only required communicating one way from native code to JavaScript.

Core Mechanic

A UIWebView has a method stringByEvaluatingJavaScriptFromString that allows your native code to run some JavaScript on your web code. This function takes a string so you can do something like:

NSString *javascript = @"alert('Hello World');";
[webView stringByEvaluatingJavaScriptFromString:javascript];

Which will trigger an alert. Not terribly useful, but its a start. We can adapt this to call any globally accessible function in JavaScript with a JSON payload:

// Native Code

// Setup your data
NSDictionary *data = @{
    @"foo": @"bar"
};

// Serialise to JSON string
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:(NSJSONWritingOptions) 0 error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

// Craft JavaScript call
NSString *javascript = [NSString stringWithFormat: @"window.handleData('%@')", jsonString];
// JavaScript Code
window.handleData = function handleData(jsonString) {
    var data = JSON.parse(jsonString);
    console.log(data);
};

Making it more robust

The above works fine under some conditions but will fall over when you have characters that need to be escaped in the data payload. To improve upon this we need to Base64 encode the JSON string before we send it to JavaScript. We then decode it before trying to parse it.

// Native Code

// Setup your data
NSDictionary *data = @{
    @"foo": @"bar"
};

// Serialise to JSON string
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:(NSJSONWritingOptions) 0 error:nil];
NSString *base64JsonString = [jsonData base64EncodedStringWithOptions:0];

// Craft JavaScript call
NSString *javascript = [NSString stringWithFormat: @"window.handleData('%@')", base64JsonString];
// JavaScript Code
window.handleData = function handleData(base64Data) {
    var jsonString = atob(base64Data);
    var data = JSON.parse(jsonString);
    console.log(data);
};