Thursday, September 13, 2012

Bridging between JavaScript & Objective-C for Hybrid iOS Apps

Hi Guyz, in this section i will write about the bridging between the native Objective-C code with the web portion(JavaScript/HTML) code. The iOS hybrid application delivers a native experience on iOS devices by wrapping the mobile Web storefront with a native shell. The native shell elements are coded using the iOS Software Development Kit (SDK), while the storefront is accessed using the mobile Web interface. That generally means  hybrid app is a combination of web app + native iOS features. We use UIWebView for loading the webapp (linking URL) and we can have our own iOS feature included with it. So, In this section, i will be describing about how to call native Objective-C method from particular button in web portion and vice versa.

Call Javascript function from Objective-C

  // In your Javascript files:
    function myJavascriptFunction () {
        // Do whatever your want!
        }
       // -----------------------------------
       // And in your Objective-C code:
    // Call Javascript function from Objective-C:

    [webViewObject stringByEvaluatingJavaScriptFromString:@"myJavascriptFunction()"];

Call Objective-C function from Javascript

// In Objective-C
    - viewDidLoad {
        webView = [[UIWebView alloc] init];
        // Register the UIWebViewDelegate in order to shouldStartLoadWithRequest to be called (next function)
        webView.delegate = self; 
    }
    // This function is called on all location change :
    - (BOOL)webView:(UIWebView *)webView  shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {

    // Intercept custom location change, URL begins with "js-call:"
    if ([[[request URL] absoluteString] hasPrefix:@"js-call:"]) {
        // Extract the selector name from the URL
        NSArray *components = [requestString componentsSeparatedByString:@":"];
        NSString *functionName = [components objectAtIndex:1];
        // Call the given selector
        [self performSelector:NSSelectorFromString(functionName)];
        // Cancel the location change
        return NO;
    }
    // Accept this location change
    return YES;
}
    - (void)myObjectiveCFunction {
        // Do whatever you want!
    }

    // -----------------------------------
 // In Javascript files
    // Now in your javascript simply do this to call your objective-c function:
       window.location = "js-call:myObjectiveCFunction";
Note : There is weird but apprehensible bugs with this practice: a lot of javascript/html stuff get broken when we cancel a location change. So, if you are having such problem then the next better solution will be:  instead of changing the document location in Javascript file, we create an IFrame and set its location to a value that trigger the shouldStartLoadWithRequest method.
 // In Javascript files (a better way)
           var iframe = document.createElement("IFRAME");
           iframe.setAttribute("src", "js-frame:myObjectiveCFunction"; 
          document.documentElement.appendChild(iframe);
           iframe.parentNode.removeChild(iframe);
           iframe = null;
    
 /* Example with multiple parameters sending from Javascript to Objective-C */
1. In Javascript file
    // Use this in javascript to request native objective-c code
    // functionName : string
    // args : array of arguments

    call : function call(functionName, args) {
        var iframe = document.createElement("IFRAME");
        iframe.setAttribute("src", "js-frame:" + functionName + ":" + encodeURIComponent(JSON.stringify(args)));
        document.documentElement.appendChild(iframe);
        iframe.parentNode.removeChild(iframe);
        iframe = null;
    }

2. In Objective-C file
   // This selector is called when something is loaded in our webview like:
    // - main html document
    // - sub iframes document

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

    NSString *requestString = [[request URL] absoluteString];
    if ([requestString hasPrefix:@"js-frame:"]) {
        NSArray *components = [requestString componentsSeparatedByString:@":"];
        NSString *functionName = (NSString*)[components objectAtIndex:1];
        NSString *argsAsString = [(NSString*)[components objectAtIndex:2]
                                  stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        // Instanciate JSON parser library
        // include json library for json parsing or formating of parameters sent by javascript
        // or you may have send the parameters as a URL componenets separated by ":" without JSON
        //For this example Javascript is sending parameters as a json string so we need to parse it on client side

        SBJSON *json = [ SBJSON new ];
        NSArray *args = (NSArray*)[json objectWithString:argsAsString error:nil];
        // args are the parameters sent from web to your client side in iPhone/iPad, so handle this as per your requirement
        //and generate/call respective native Objective-C functions

        return NO;
    }
   
    return YES;
}




4 comments:

  1. Thank u Samir.
    It Will Work in Code.. Thanks Lot Again

    ReplyDelete
  2. perform selector may cause a leak . errrrrrooooor x-(

    ReplyDelete
  3. JavaScriptCore.framework is available in iOS7. This library may be more helpful: https://github.com/liaojinxing/HybridBridge

    ReplyDelete
  4. Can you please show how to do this in a example. not getting how to create javascript file and exactly what to do.
    Thanks.

    ReplyDelete