Call Objective-C methods from the Genero application

Call Objective-C methods from Genero application for the GWC hybrid mode for iOS.

The principle is to use a custom URL call in the javascript framework of Genero application, with a custom URL Scheme. When the native application traps this call in the shouldStartLoadWithRequest method (WebViewController.m file), you can trigger custom actions.

See Implementing Custom URL Schemes in the iOS documentation.

Example

In ghcWebView.m, who act as an UIWebViewDelegate, this method is implemented:
#pragma mark UIWebViewDelegate
// This selector is called when something is loaded in our webview
// By something I don't mean anything but just "some" :
// - main html document
// - sub iframes document
//
- (BOOL)webView:(UIWebView *)webView2
shouldStartLoadWithRequest:(NSURLRequest *)request
 navigationType:(UIWebViewNavigationType)navigationType {
  NSString *requestString = [[request URL] absoluteString];
NSLog(@"request : %@",requestString);

if ([requestString hasPrefix:@"ghc-frame:"]) {
  NSArray *components = [requestString componentsSeparatedByString:@":"];
  NSString *function = (NSString*)[components objectAtIndex:1];
    int callbackId = [((NSString*)[components objectAtIndex:2]) intValue];
  NSString *argsAsString = [(NSString*)[components objectAtIndex:3]
    stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  if ([NSJSONSerialization isValidJSONObject:components]) {
    NSLog(@"isValid");
  } else {
    NSLog(@"Is NOT VALID");
  }

  NSLog(@"components %@", components);

  NSArray *args = [NSArray arrayWithObject:argsAsString];
  [self handleCall:function callbackId:callbackId args:args];
  return NO;
}
return YES;
}

#pragma mark gwc ghc bridge

// Call this function when you have results to send back to javascript callbacks
// callbackId : int comes from handleCall function
// args: list of objects to send to the javascript callback
- (void)returnResult:(int)callbackId args:(id)arg, ...;
{
  if (callbackId==0) return;

  va_list argsList;
  NSMutableArray *resultArray = [[NSMutableArray alloc] init];

  if(arg != nil){
    [resultArray addObject:arg];
    va_start(argsList, arg);
    while((arg = va_arg(argsList, id)) != nil)
      [resultArray addObject:arg];
    va_end(argsList);
  }
  NSString *resultArrayString = [resultArray objectAtIndex:0];

  // We need to perform selector with afterDelay 0
  // in order to avoid weird recursion stop
  // when calling NativeBridge in a recursion more
  // than 200 times
  [self performSelector:@selector(returnResultAfterDelay:) 
withObject:[NSString
stringWithFormat:@"gwc.ghc.resultForCallback('%d',['%@'])",
callbackId,resultArrayString]
afterDelay:0];
}

-(void)returnResultAfterDelay:(NSString*)str {

  // Now perform this selector with waitUntilDone:NO
  // in order to get a huge speed boost! 
  NSLog(@"str : %@", str);
  [self performSelectorOnMainThread:
@selector(stringByEvaluatingJavaScriptFromString:)
withObject:str waitUntilDone:NO];
}

// Implements all you native function in this one,
// by matching 'functionName' and parsing 'args'
// Use 'callbackId' with 'returnResult' selector when
// you get some results to send back to javascript
- (void)handleCall:(NSString*)functionName
 callbackId:(int)callbackId args:(NSArray*)args
{

  if ([functionName isEqualToString:@"setKeepAliveURL"]) {

    if ([args count]!=1) {
      NSLog(@"setKeepAliveURL wait exactly 1 arguments!");
      return;
    }

    NSMutableString *kaurl = (NSMutableString*)[args objectAtIndex:0];
    [kaurl replaceOccurrencesOfString:@"\"" withString:@""
 options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [kaurl length])];

    // we send keepAliveUrl to delegate object
     ghcAppDelegate *delegate =
 (ghcAppDelegate *)[[UIApplication sharedApplication] delegate];
    [delegate setKeepAliveURL:kaurl];

  } else if ([functionName isEqualToString:@"nativeYesNo"]) {

    if ([args count]!=1) {
      NSLog(@"nativeYesNo wait exactly one argument!");
      return;
    }

    NSMutableString *message = (NSMutableString*)[args objectAtIndex:0];
    [message replaceOccurrencesOfString:@"\"" withString:@""
options:NSCaseInsensitiveSearch range:NSMakeRange(0, [message length])];

    alertCallbackId = callbackId;
    UIAlertView *alert=[[UIAlertView alloc]
 initWithTitle:nil message:message delegate:self
cancelButtonTitle:@"Cancel" otherButtonTitles:@"Ok", nil];
    [alert show];
  } else {
    NSLog(@"Unimplemented method '%@'",functionName);
  }
}

// AlertView that show how to return asynchronous results
- (void)alertView:(UIAlertView *)alertView
 clickedButtonAtIndex:(NSInteger)buttonIndex
{
  if (!alertCallbackId) return;

  NSLog(@"prompt result : %d",buttonIndex);

  NSString * result = buttonIndex==1?@"YES":@"NO";
  NSLog(@"result : %@", result);
  [self returnResult:alertCallbackId args:result,nil];

  alertCallbackId = 0;
}
And in JavaScriptâ„¢, the following code is handling the bridge:
gwc.ghc = {

  callbacksCount : 1,
  callbacks : {},
  // Automatically called by native layer when a result is available
  resultForCallback : function resultForCallback(callbackId, resultArray) {
    try {
      var callback = gwc.ghc.callbacks[callbackId];
      if (!callback) return;

      callback.apply(null,resultArray);
    } catch(e) {alert(e)}
    },

  // Use this in javascript to request native objective-c code
  // functionName
  // args : array of arguments
  // callback : function with n-arguments that is going to be
  // called when the native code
returned
  invoke : function invoke (functionName, args, callback) {

    var hasCallback = callback && typeof callback == "function";
    var callbackId = hasCallback ? gwc.ghc.callbacksCount++ : 0;
    if (hasCallback) {
      gwc.ghc.callbacks[callbackId] = callback;
    }
    if (isAndroid) {
      GHCAndroid[functionName](encodeURIComponent(JSON.stringify(args)),
callbackId);
    } else {
      // GHC-ios
      var iframe = document.createElement("IFRAME");
      iframe.setAttribute("src", "ghc-frame:" + functionName + ":" + callbackId+
":" + encodeURIComponent(JSON.stringify(args)));
      document.documentElement.appendChild(iframe);
      iframe.parentNode.removeChild(iframe);
      iframe = null;
    }
  },

  callApi : function (functionName, args) {
    // this fails ->
    //this.api[functionName](JSON.parse(args));
    this.api[functionName](args);
  },

  api : {}
}

gwc.ghc.api.getKeepAliveURL = function () {
  var url = gwc.api.keepAliveURL();
  var json = {};
  json.url = url;
  gwc.ghc.invoke("setKeepAliveURL", url);
}