{"id":667,"date":"2013-01-12T16:26:02","date_gmt":"2013-01-12T10:56:02","guid":{"rendered":"http:\/\/ramkulkarni.com\/blog\/?p=667"},"modified":"2013-01-12T16:26:02","modified_gmt":"2013-01-12T10:56:02","slug":"calling-objective-c-function-from-javascript-in-ios-applications","status":"publish","type":"post","link":"http:\/\/ramkulkarni.com\/blog\/calling-objective-c-function-from-javascript-in-ios-applications\/","title":{"rendered":"Calling Objective-C code from JavaScript in iOS applications"},"content":{"rendered":"<p>In the last post I described how to\u00a0<a title=\"Creating iOS Application with HTML User Interface\" href=\"http:\/\/ramkulkarni.com\/blog\/creating-ios-application-with-html-user-interface\/\" target=\"_blank\">Create iOS Application with HTML User Interface<\/a>\u00a0. In this post I am going to describe how to access Objective-C code from JavaScript running in the WebView. It is not as easy as it is in Android. There is no direct API in UIWebView to call native code, unlike in Android which provides WebView.<a href=\"http:\/\/developer.android.com\/reference\/android\/webkit\/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)\" target=\"_blank\">addJavascriptInterface<\/a>\u00a0function.<\/p>\n<p>However, there is a work-around. We can intercept each URL before it is being loaded in the WebView. So to call the native code, we will have to construct a URL and pass method name and argument as part of the URL. We then set this URL to window.location in the JavaScript and intercept it in our ViewController.<br \/>\nHowever most examples I have seen (including PhoneGap) create an instance of iframe and set its source to the URL &#8211;<br \/>\n<!--more--><\/p>\n<pre style=\"color: #000020; background: #f6f8ff;\">function openCustomURLinIFrame(src)\n{\n    var rootElm = document.documentElement;\n    var newFrameElm = document.createElement(\"IFRAME\");\n    newFrameElm.setAttribute(\"src\",src);\n    rootElm.appendChild(newFrameElm);\n    \/\/remove the frame now\n    newFrameElm.parentNode.removeChild(newFrameElm);\n}<\/pre>\n<p>Following JavaScript function creates a JSON string from function name and arguments and then calls\u00a0openCustomURLinIFrame.<\/p>\n<pre style=\"color: #000020; background: #f6f8ff;\">function calliOSFunction(functionName, args, successCallback, errorCallback)\n{\n    var url = \"js2ios:\/\/\";\n\n    var callInfo = {};\n    callInfo.functionname = functionName;\n    if (successCallback)\n    {\n        callInfo.success = successCallback;\n    }\n    if (errorCallback)\n    {\n        callInfo.error = errorCallback;\n    }\n    if (args)\n    {\n        callInfo.args = args;\n    }\n\n    url += JSON.stringify(callInfo)\n\n    openCustomURLinIFrame(url);\n}<\/pre>\n<p>Note that I have created the url with custom protocol &#8216;js2ios&#8217;. This is just a marker protocol which we will intercept and process in the ViewController of iOS application. Since the code to call native method is asynchronous and does not return the result of the execution, we will have to pass success and error callback functions. These callback functions would be called from the native code.<\/p>\n<pre style=\"color: #000020; background: #f6f8ff;\">calliOSFunction(\"sayHello\", [\"Ram\"], \"onSuccess\", \"onError\");\n\nfunction onSuccess (ret)\n{\n    if (ret)\n    {\n        var obj = JSON.parse(ret);\n        document.write(obj.result);\n    }\n}\n\nfunction onError (ret)\n{\n    if (ret)\n    {\n        var obj = JSON.parse(ret);\n        document.write(obj.error);\n    }\n}<\/pre>\n<p>In the above code I am passing callback functions as string, which is not the best way. But we will see how to fix this later.<br \/>\nLet&#8217;s now see what we need to implement in Objective-C to execute &#8216;sayHello&#8217; method, when called from JavaScript.<\/p>\n<p>The ViewController in our application should implement\u00a0UIWebViewDelegate protocol &#8211;<\/p>\n<div style=\"background: white; overflow: auto; width: auto; color: black; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\n<pre style=\"margin: 0; line-height: 125%;\">@interface RKViewController : UIViewController&lt;UIWebViewDelegate&gt;\n{\n    IBOutlet UIWebView *webView;\n}\n@end<\/pre>\n<\/div>\n<p>Then we implement shouldStartLoadWithRequest method of UIWebViewDelegate protocol (interface). This function would be called before loading each url in the WebView. If you return true from this function, the WebView will load the URL, else it would not. So if the url contins our custom protocol, then we would process it and return false, so that WebView does not attempt to load it.<br \/>\nFollowing code implements shouldStartLoadWithRequest\u00a0and also other functions to process our custom protocol (this code goes in ViewController.m) &#8211;<br \/>\n<!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: white; overflow: auto; width: auto; color: black; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\n<pre style=\"margin: 0; line-height: 125%;\">- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {\n\n    NSURL *url = [request URL];\n    NSString *urlStr = url.absoluteString;\n\n    return [self processURL:urlStr];\n\n}\n\n- (BOOL) processURL:(NSString *) url\n{\n    NSString *urlStr = [NSString stringWithString:url];\n\n    NSString *protocolPrefix = @\"js2ios:\/\/\";\n\n    \/\/process only our custom protocol\n    if ([[urlStr lowercaseString] hasPrefix:protocolPrefix])\n    {\n        \/\/strip protocol from the URL. We will get input to call a native method\n        urlStr = [urlStr substringFromIndex:protocolPrefix.length];\n\n        \/\/Decode the url string\n        urlStr = [urlStr stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];\n\n        NSError *jsonError;\n\n        \/\/parse JSON input in the URL\n        NSDictionary *callInfo = [NSJSONSerialization\n                                  JSONObjectWithData:[urlStr dataUsingEncoding:NSUTF8StringEncoding]\n                                  options:kNilOptions\n                                  error:&amp;jsonError];\n\n        \/\/check if there was error in parsing JSON input\n        if (jsonError != nil)\n        {\n            NSLog(@\"Error parsing JSON for the url %@\",url);\n            return NO;\n        }\n\n        \/\/Get function name. It is a required input\n        NSString *functionName = [callInfo objectForKey:@\"functionname\"];\n        if (functionName == nil)\n        {\n            NSLog(@\"Missing function name\");\n            return NO;\n        }\n\n        NSString *successCallback = [callInfo objectForKey:@\"success\"];\n        NSString *errorCallback = [callInfo objectForKey:@\"error\"];\n        NSArray *argsArray = [callInfo objectForKey:@\"args\"];\n\n        [self callNativeFunction:functionName withArgs:argsArray onSuccess:successCallback onError:errorCallback];\n\n        \/\/Do not load this url in the WebView\n        return NO;\n\n    }\n\n    return YES;\n}\n\n- (void) callNativeFunction:(NSString *) name withArgs:(NSArray *) args onSuccess:(NSString *) successCallback onError:(NSString *) errorCallback\n{\n    \/\/We only know how to process sayHello\n    if ([name compare:@\"sayHello\" options:NSCaseInsensitiveSearch] == NSOrderedSame)\n    {\n        if (args.count &gt; 0)\n        {\n            NSString *resultStr = [NSString stringWithFormat:@\"Hello %@ !\", [args objectAtIndex:0]];\n\n            [self callSuccessCallback:successCallback withRetValue:resultStr forFunction:name];\n        }\n        else\n        {\n            NSString *resultStr = [NSString stringWithFormat:@\"Error calling function %@. Error : Missing argument\", name];\n            [self callErrorCallback:errorCallback withMessage:resultStr];\n        }\n    }\n    else\n    {\n        \/\/Unknown function called from JavaScript\n        NSString *resultStr = [NSString stringWithFormat:@\"Cannot process function %@. Function not found\", name];\n        [self callErrorCallback:errorCallback withMessage:resultStr];\n\n    }\n}\n\n-(void) callErrorCallback:(NSString *) name withMessage:(NSString *) msg\n{\n    if (name != nil)\n    {\n        \/\/call error handler\n\n        NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] init];\n        [resultDict setObject:msg forKey:@\"error\"];\n        [self callJSFunction:name withArgs:resultDict];\n    }\n    else\n    {\n        NSLog(@\"%@\",msg);\n    }\n\n}\n\n-(void) callSuccessCallback:(NSString *) name withRetValue:(id) retValue forFunction:(NSString *) funcName\n{\n    if (name != nil)\n    {\n        \/\/call succes handler\n\n        NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] init];\n        [resultDict setObject:retValue forKey:@\"result\"];\n        [self callJSFunction:name withArgs:resultDict];\n    }\n    else\n    {\n        NSLog(@\"Result of function %@ = %@\", funcName,retValue);\n    }\n\n}\n\n-(void) callJSFunction:(NSString *) name withArgs:(NSMutableDictionary *) args\n{\n    NSError *jsonError;\n\n    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:args options:0 error:&amp;jsonError];\n\n    if (jsonError != nil)\n    {\n        \/\/call error callback function here\n        NSLog(@\"Error creating JSON from the response  : %@\",[jsonError localizedDescription]);\n        return;\n    }\n\n    \/\/initWithBytes:length:encoding\n    NSString *jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];\n\n    NSLog(@\"jsonStr = %@\", jsonStr);\n\n    if (jsonStr == nil)\n    {\n        NSLog(@\"jsonStr is null. count = %d\", [args count]);\n    }\n\n    [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@\"%@('%@');\",name,jsonStr]];\n}<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<p>And you need to modify viewDidLoad function to set delegate of WebView to ViewController instance &#8211;<br \/>\n<!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: white; overflow: auto; width: auto; color: black; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\n<pre style=\"margin: 0; line-height: 125%;\">- (void)viewDidLoad\n{\n    [super viewDidLoad];\n\n    NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@\"index\" ofType:@\"html\" inDirectory:@\"wwwroot\"]];\n\n    NSURLRequest *req = [NSURLRequest requestWithURL:url];\n\n    \/\/Set delegate for WebView\n    [webView setDelegate:self];\n\n    [webView loadRequest:req];\n\n}<\/pre>\n<\/div>\n<p>When you run the application, &#8216;sayHello&#8217; function would be called from the JavaScript in index.html and executed by Objective-C code above. If the function is successfully executed then success callback function of JavaScript would be called.<br \/>\nNotice that in the above code stringByEvaluatingJavaScriptFromString method of UIWebView class is called to execute JavaScript code from Objective-C code.<br \/>\nNow change the function to be called in index.html to &#8216;sayHello1&#8217; and run the application. You will see error message &#8216;Function not found&#8217; in the application. This is displayed by calling JavaScript error callback function.<\/p>\n<p>The above example shows how to call native code from JavaScript and vice versa. But the solution is not very elegant. I wanted to implement a generic solution which would make this process very easy. Also \u00a0I wanted to fix the issue of having to pass callback function as string (function name) to JavaScript function &#8216;calliOSFunction&#8217;. I also wanted to support inline function (closures) for success and callback functions.<br \/>\nSo I created a small framework that abstracts many of the common functionality and provides a parent class for ViewController and a JavaScript file to be included in the index.html &#8211;<\/p>\n<p><a href=\"http:\/\/ramkulkarni.com\/temp\/2013-01-12\/WebViewController.h\" target=\"_blank\">WebViewController.h<\/a><br \/>\n<a href=\"http:\/\/ramkulkarni.com\/temp\/2013-01-12\/WebViewController.m\" target=\"_blank\">WebViewController.m<br \/>\n<\/a><a href=\"http:\/\/ramkulkarni.com\/temp\/2013-01-12\/WebViewInterface.h\" target=\"_blank\">WebViewInterface.h<\/a><br \/>\n<a href=\"http:\/\/ramkulkarni.com\/temp\/2013-01-12\/iosBridge.js\" target=\"_blank\">iosBridge.js<\/a><\/p>\n<p>WebViewController and WebViewInterface files should go in the source folder of your project, along with other .h and .m files. iosBridge.js should go in the same folder as index.html &#8211; in our example it is in wwwroot. To see newly added class files in the project navigator in Xcode, CTRL+Click on the source folder (same name as project name) and select &#8216;Add Files to &#8230;&#8221; option.<\/p>\n<p>Note that you still have to create WebView in .xib file and connect to webView outlet in WebViewController.h as described in my earlier <a title=\"Creating iOS Application with HTML User Interface\" href=\"http:\/\/ramkulkarni.com\/blog\/creating-ios-application-with-html-user-interface\/\" target=\"_blank\">post<\/a>.<\/p>\n<p>Using this framework, the index.html file looks as follows &#8211;<\/p>\n<pre style=\"color: #000020; background: #f6f8ff;\">&lt;script src=\"iosBridge.js\" type=\"text\/javascript\"&gt;&lt;\/script&gt;\n\n&lt;script type=\"text\/javascript\"&gt;\n\n    function onSuccess (ret)\n    {\n        if (ret)\n        {\n            document.write(ret.result);\n        }\n    }\n\n    calliOSFunction(\"sayHello\",[\"Ram\"],onSuccess,\n        function(ret)\n        {\n            if (ret)\n            document.write(\"Error executing native function : &lt;br&gt;\" + ret.message);\n        }\n    );\n\n&lt;\/script&gt;<\/pre>\n<p>We use calliOSFunction to call native code. You can either pass function name or inline function for success and error callbacks.<\/p>\n<p>Viewcontroller.h &#8211;<br \/>\n<!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: white; overflow: auto; width: auto; color: black; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\n<pre style=\"margin: 0; line-height: 125%;\">#import \"WebViewController.h\"\n\n@interface RKViewController : WebViewController\n\n@end<\/pre>\n<\/div>\n<p>And ViewController.m &#8211;<\/p>\n<div style=\"background: white; overflow: auto; width: auto; color: black; border: solid gray; border-width: .1em .1em .1em .8em; padding: .2em .6em;\">\n<pre style=\"margin: 0; line-height: 125%;\">#import \"RKViewController.h\"\n\n@interface RKViewController ()\n\n@end\n\n@implementation RKViewController\n\n- (NSString *) getInitialPageName\n{\n    return @\"index.html\";\n}\n\n- (id) processFunctionFromJS:(NSString *) name withArgs:(NSArray*) args error:(NSError **) error\n{\n\n    if ([name compare:@\"sayHello\" options:NSCaseInsensitiveSearch] == NSOrderedSame)\n    {\n        if (args.count &gt; 0)\n        {\n            return [NSString stringWithFormat:@\"Hello %@ !\", [args objectAtIndex:0]];\n        }\n        else\n        {\n            NSString *resultStr = [NSString stringWithFormat:@\"Missing argument in function %@\", name];\n            [self createError:error withCode:-1 withMessage:resultStr];\n            return nil;\n        }\n    }\n    else\n    {\n        NSString *resultStr = [NSString stringWithFormat:@\"Function '%@' not found\", name];\n        [self createError:error withCode:-1 withMessage:resultStr];\n        return nil;\n    }\n}\n\n@end<\/pre>\n<\/div>\n<p>You need to override two methods in the ViewController &#8211;<\/p>\n<p>1. &#8211; (NSString *) getInitialPageName<br \/>\nThis function should return page name to be loaded in the WebView. The framework looks for this file in wwwroot folder. You can also return a url from this function, starting with http or https.<\/p>\n<p>2. &#8211; (id) processFunctionFromJS:(NSString *) name withArgs:(NSArray*) args error:(NSError **) error<br \/>\nYou would write code to process function call from JavaScript here.<\/p>\n<p>I believe this framework would make calling Objective-C from JavaScript a bit more easier.<\/p>\n<p>-Ram Kulkarni<\/p>\n<p><strong>UPDATE<\/strong> : See &#8216;<a title=\"Framework for interacting with embedded WebView in iOS application\" href=\"http:\/\/ramkulkarni.com\/blog\/framework-for-interacting-with-embedded-webview-in-ios-application\/\">Framework for interacting with embedded WebView in iOS application<\/a>&#8216; for the updated framework.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the last post I described how to\u00a0Create iOS Application with HTML User Interface\u00a0. In this post I am going to describe how to access Objective-C code from JavaScript running in the WebView. It is not as easy as it is in Android. There is no direct API in UIWebView to call native code, unlike &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/ramkulkarni.com\/blog\/calling-objective-c-function-from-javascript-in-ios-applications\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Calling Objective-C code from JavaScript in iOS applications&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"Calling Objective-C function from JavaScript in iOS applications http:\/\/wp.me\/p2g9O8-aL","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[15,20,1],"tags":[14,5,63],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p2g9O8-aL","jetpack-related-posts":[],"_links":{"self":[{"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/posts\/667"}],"collection":[{"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/comments?post=667"}],"version-history":[{"count":1,"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/posts\/667\/revisions"}],"predecessor-version":[{"id":1998,"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/posts\/667\/revisions\/1998"}],"wp:attachment":[{"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/media?parent=667"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/categories?post=667"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/ramkulkarni.com\/blog\/wp-json\/wp\/v2\/tags?post=667"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}