How to invoke Objective-C method from Javascript and send back data to Javascript in iOS?

Krishnan picture Krishnan · Mar 22, 2012 · Viewed 40.6k times · Source

In iOS, how can I call an Objective-C method from Javascript in a UIWebView and have it send data back to the Javascript? I know that this could be done on OS X using the Webkit library, but is this possible on iOS? How does PhoneGap achieve this?

Answer

zekel picture zekel · Mar 22, 2012

There is an API to call JavaScript directly from Objective-C, but you cannot call Objective-C directly from Javascript.

How to tell your Objective-C code to do something from the Javascript in your WebView

You have to serialize your Javascript action into a special URL and intercept that URL in the UIWebView's delegate's shouldStartLoadWithRequest method.

- (BOOL)webView:(UIWebView *)webView
        shouldStartLoadWithRequest:(NSURLRequest *)request
                    navigationType:(UIWebViewNavigationType)navigationType;

There you can deserialize that special URL and interpret it to do what you want on the Objective-C side. (You should return NO in the above shouldStartLoadWithRequest method so the UIWebView doesn't use your bogus URL to actually make an HTTP request to load a webpage.)

How to Run Javascript Code from Objective-C

Then you can run Javascript from Objective-C by calling this method on your webview.

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

Example Code

I recommend using a bogus URL scheme so it will be easy to tell the difference between your action URLs and legit requests. You can make this request in the Javascript along these lines:

// JavaScript to send an action to your Objective-C code
var myAppName = 'myfakeappname';
var myActionType = 'myJavascriptActionType';
var myActionParameters = {}; // put extra info into a dict if you need it

// (separating the actionType from parameters makes it easier to parse in ObjC.)
var jsonString = (JSON.stringify(myActionParameters));
var escapedJsonParameters = escape(jsonString);
var url = myAppName + '://' + myActionType + "#" + escapedJsonParameters;
document.location.href = url;

Then in the UIWebView.delegate's shouldStartLoadWithRequest method, you can inspect the URL scheme and fragment to check if it's a normal request or one of your special actions. (The fragment of a URL is what comes after the #.)

- (BOOL)webView:(UIWebView *)webView
        shouldStartLoadWithRequest:(NSURLRequest *)request
                    navigationType:(UIWebViewNavigationType)navigationType {

    // these need to match the values defined in your JavaScript
    NSString *myAppScheme = @"myfakeappname";
    NSString *myActionType = @"myJavascriptActionType";

    // ignore legit webview requests so they load normally
    if (![request.URL.scheme isEqualToString:myAppScheme]) {
        return YES;
    }

    // get the action from the path
    NSString *actionType = request.URL.host;
    // deserialize the request JSON
    NSString *jsonDictString = [request.URL.fragment stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];

    // look at the actionType and do whatever you want here
    if ([actionType isEqualToString:myActionType]) {
        // do something in response to your javascript action
        // if you used an action parameters dict, deserialize and inspect it here
    }


    // make sure to return NO so that your webview doesn't try to load your made-up URL
    return NO;
}

(Read this answer if you need help deserializing your json string into an NSDictionary.)