Trigger objective C method from javascript using JavaScriptCore in iOS 7 in ViewControllers

14,074

The Objective-C interface in the JavascriptCore framework introduced with iOS 7 permits calling Objective-C methods from Javascript. For a great intro to these features, check out the 2013 WWDC introduction "Integrating JavaScript into Native Apps" session on Apple's developer network: https://developer.apple.com/videos/wwdc/2013/?id=615

It does have a brief section towards the end on WebView (MacOs only not iOS)

The sample code below shows how to implement what you want for iOS.

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0,40,320,320)];
    webView.delegate = self;
    [self.view addSubview:webView];
     NSString *pageSource = @"<!DOCTYPE html> <html> <head> </head> <body> <h1>My Mobile App</h1> <p>Please enter the Details</p> <form name=\"feedback\" method=\"post\" action=\"mailto:[email protected]\"> <!-- Form elements will go in here --> </form> <form name=\"inputform\"> <input type=\"button\" onClick=\"submitButton('My Test Parameter')\" value=\"submit\"> </form> </body> </html>";
    [webView loadHTMLString:pageSource baseURL:nil];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    JSContext *context =  [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // Undocumented access
    context[@"submitButton"] = ^(NSString *param1) {
        [self yourObjectiveCMethod:param1];
    };
}

- (void)yourObjectiveCMethod:(NSString *)param1 {
    NSLog(@"User clicked submit. param1=%@", param1);
}

Things to note:

  • Accessing the JSContext of a UIWebView is undocumented. Two techniques are known (see Access the JavaScriptCore engine of a UIWebView). The first technique is used here.
  • You don't need to define the javascript function "submitButton" inside the page, you will define the function from Objective-C using the webView's JSContext
  • A page load in a webview causes its JSContext to get replaced, hence you should implement a delegate for the UIWebView and define your Objective-C callback in your implementation of the selector for -webViewDidFinishLoad:
  • I've assumed you will want to pass parameters to this callback, so I've shown a sample parameter. Although this is not covered in the video tutorial mentioned above (or the PDF equivalent), looking at JSValue.h shows that JavascriptCore offers built-in conversion between the following Objective-C and Javascript types:

.

  Objective-C type  |   JavaScript type
--------------------+---------------------
        nil         |     undefined
       NSNull       |        null
      NSString      |       string
      NSNumber      |   number, boolean
    NSDictionary    |   Object object
      NSArray       |    Array object
       NSDate       |     Date object
      NSBlock *     |   Function object *
         id **      |   Wrapper object **
       Class ***    | Constructor object ***

* Instances of NSBlock with supported arguments types will be presented to
JavaScript as a callable Function object. For more information on supported
argument types see JSExport.h. If a JavaScript Function originating from an
Objective-C block is converted back to an Objective-C object the block will
be returned. All other JavaScript functions will be converted in the same
manner as a JavaScript object of type Object.

** For Objective-C instances that do not derive from the set of types listed
above, a wrapper object to provide a retaining handle to the Objective-C
instance from JavaScript. For more information on these wrapper objects, see
JSExport.h. When a JavaScript wrapper object is converted back to Objective-C
the Objective-C instance being retained by the wrapper is returned.

*** For Objective-C Class objects a constructor object containing exported
class methods will be returned. See JSExport.h for more information on
constructor objects.
Share:
14,074
user3283486
Author by

user3283486

Updated on July 29, 2022

Comments

  • user3283486
    user3283486 almost 2 years

    I am loading a url in the webview which has below HTML and javascript function call. Now I am looking into when user touches submit button in webview it should call any method in viewController. Webview should load url(webview delegate) is one approach but is there any new way to get know when javascript function is called by webview should inform viewcontroller or trigger any method in view controller.

    I am not looking for this solution this we need to

     - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    
        NSLog(@"passed data from web view : %@",[[request URL] query]);
    
        if([[[request URL] query] isEqualToString:@"clickedOnLineNo"])
        {
    //Do your action here
    
        }
    

    We have to navigate to a fake URL from your javascript method like,

    window.location = "someLink://yourApp/form_Submitted:param1:param2:param3";  
    

    on a button click or your required action.

    But is there any new possibility from JavaScriptCore to achieve the same.

  • user3283486
    user3283486 about 10 years
    JS context works similar somehow like [webView stringByEvaluatingJavaScriptFromString:@"document.getElement‌​ById('lastname').val‌​ue;"]; but it offers more than that. Thanks Mike for the explanation.
  • Mike
    Mike about 10 years
    @TMacGyver - Apple has not formally documented the Objective-C extensions they added to the JavaScriptCore framework when iOS7 was released, so your mileage may vary for either of the 2 known techniques. I haven't submitted an app using either technique, however there have been reports on SO of successful submissions using the first technique (as used in the example above). See mprudhom's answer to stackoverflow.com/questions/18920536/…