Customizations and Extensions / Use front calls |
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.
#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; }
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); }