您的位置:首页 > 移动开发 > Objective-C

Objective-c与JS交互

2013-12-27 17:24 253 查看
Let’s change the subject: this time no more talks about memory but always on UIWebView component. When we use this component for something else than just displaying webpages, like building UI with HTML, Javascript, … We often want to call Javascript functions
from objective C and the opposite.


Call Javascript function from Objective-C:

The first move is easily done with the following piece of code:
// In your Javascript files:
    function myJavascriptFunction () {
    
      // Do whatever your want!
    
    }
  
  // -----------------------------------
  
  // And in your Objective-C code:
  // Call Javascript function from Objective-C:
    [webview stringByEvaluatingJavaScriptFromString:@"myJavascriptFunction()"];



Call Objective-C function from Javascript:

But calling objective-c from a Javascript function is not easy as Iphone SDK doesn’t offer any native way to do this! So we have to use any king of hack to do this …

The most known, used and buggy practice is to register a UIWebViewDelegate on
your web view and « catch-and-immediatly-cancel » a location change done in javascript.

(a very extremely plenty much advised practice!)
// In Objective-C
  - someFunctionOnInit {
    
    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 *)webView2 
          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 *function = [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!
   
  }

  // -----------------------------------
  
  // Now in your javascript simply do this to call your objective-c function:
  // /!\ But for those who just read title and code, take care, this is a buggy practice /!\\n  window.location = "js-call:myObjectiveCFunction";



What’s wrong with UIWebViewDelegate, shouldStartLoadWithRequest and location change ?

There is weird but apprehensible bugs with this practice:

a lot of javascript/html stuff get broken when we cancel a location change:

All setInterval and setTimeout immediatly stop on location change

Every innerHTML won’t work after a canceled location change!

You may get other really weird bugs, really hard to diagnose …

Sample application highlighting these bugs

Key files of this example:

MyWebview.m: Objective-c part, that inherit from UIWebView. Set the UIWebViewDelegate
and catch requests in shouldStartLoadWithRequest selector.

NativeBridge.js: Tiny javascript library in order to change the location
and offer a way to send arguments and receive a response.

webview-script.js: Test case script, that highlight these bugs.

In webview-script.js: InnerHTML stop working whereas textContent continues to …
document.getElementById("count").innerHTML = i;
  document.getElementById("count2").textContent = i;


But we can’t charge Apple on this bug. I mean we try to load another location in the document we are working on! The webview component may start doing stuff before the delegate call, which cancel the load …

We have to find alternative way to communicate with the native code!


Better way to call Objective-C

The only thing we have to change is in Javascript code. Instead of changing the document location, we create an IFrame and set its location to a value that trigger the shouldStartLoadWithRequest method.

And voilà!
var iframe = document.createElement("IFRAME");
  iframe.setAttribute("src", "js-frame:myObjectiveCFunction";
  document.documentElement.appendChild(iframe);
  iframe.parentNode.removeChild(iframe);
  iframe = null;


Here is another sample application, with exactly the same structures and test file.

But this time you are going to see innerHTML and setTimeout working! Again, this demo contains a library (NativeBridge.js) that allow to send arguments to native code and get back a result in javascript asynchronously, with a callback function.

Best practice example!


Free Objective-C<->Javascript library

Finally I provide the communication library under LGPL licence so it can ease your work on iphone platform! As I know that it’s really not easy ;-)

MyWebView.m: ObjectiveC part,

NativeBridge.js: Javascript side.

The code is full of comment, so you may easily use and tweak it!

Github repo

Posted by Alexandre
Poirot Oct 6th, 2010 iphone-sdk
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: