How to invoke Objective-C method from Javascript and send back data to Javascript in iOS?
There is an API to call JavaScript directly from Objective-C, but you cannot call Objective-C directly from Javascript.
How to tell your Objective-C code to do something from the Javascript in your WebView
You have to serialize your Javascript action into a special URL and intercept that URL in the UIWebView's delegate's shouldStartLoadWithRequest
method.
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType;
There you can deserialize that special URL and interpret it to do what you want on the Objective-C side. (You should return NO
in the above shouldStartLoadWithRequest
method so the UIWebView doesn't use your bogus URL to actually make an HTTP request to load a webpage.)
How to Run Javascript Code from Objective-C
Then you can run Javascript from Objective-C by calling this method on your webview.
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
Example Code
I recommend using a bogus URL scheme so it will be easy to tell the difference between your action URLs and legit requests. You can make this request in the Javascript along these lines:
// JavaScript to send an action to your Objective-C code
var myAppName = 'myfakeappname';
var myActionType = 'myJavascriptActionType';
var myActionParameters = {}; // put extra info into a dict if you need it
// (separating the actionType from parameters makes it easier to parse in ObjC.)
var jsonString = (JSON.stringify(myActionParameters));
var escapedJsonParameters = escape(jsonString);
var url = myAppName + '://' + myActionType + "#" + escapedJsonParameters;
document.location.href = url;
Then in the UIWebView.delegate
's shouldStartLoadWithRequest
method, you can inspect the URL scheme and fragment to check if it's a normal request or one of your special actions. (The fragment of a URL is what comes after the #
.)
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
// these need to match the values defined in your JavaScript
NSString *myAppScheme = @"myfakeappname";
NSString *myActionType = @"myJavascriptActionType";
// ignore legit webview requests so they load normally
if (![request.URL.scheme isEqualToString:myAppScheme]) {
return YES;
}
// get the action from the path
NSString *actionType = request.URL.host;
// deserialize the request JSON
NSString *jsonDictString = [request.URL.fragment stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
// look at the actionType and do whatever you want here
if ([actionType isEqualToString:myActionType]) {
// do something in response to your javascript action
// if you used an action parameters dict, deserialize and inspect it here
}
// make sure to return NO so that your webview doesn't try to load your made-up URL
return NO;
}
(Read this answer if you need help deserializing your json string into an NSDictionary.)
Is it possible to call an Objective-C function that returns a value to javascript?
As you are expecting, passing data from obj C to JS use stringByEvaluatingJavaScriptFromString / passing data from JS to Obj C use JSContext
<!-- save as .html and load into UIWebview -->
<!DOCTYPE html>
<html>
<head>
<script>
function loadData(x){
alert(x);
}
function myMeg(){
passDatatoObjC("success");
}
</script>
</head>
</html>
function returnSomething(x){
loadData(x);
}
function passDatatoObjC(x){
passDatatoObjC(x);
}
#pragma mark - UIWebview dataource
-(void)webViewDidFinishLoad:(UIWebView *)webView{
[self.svgWebMapView stringByEvaluatingJavaScriptFromString:@"returnSomething(10)"];
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"passDatatoObjC"] = ^(NSString *msg) {
NSLog(@"%@",msg);
});
}
Call Javascript function with parameters from iOS ObjC
You need to build a string containing the passed argument and use that as parameter for evaluateJavascript
.
Like this:
-(void) callJavascriptFunc: (NSString*)val{
NSString* script = [NSString stringWithFormat:@"exampleFuncName('%@', '\(someConstant)')", val];
[self.webview evaluateJavascript:script completionHandler: nil];
}
Depending on where the value of the parameter comes from, however, you should make sure that no code injection is possible!
Call objective-c method from javascript
The most usual approach that I used to interact from javascript within obj-c is to changing hash. On required event in your js writewindow.location.hash = '#cmd_alertMessage';
after this your - (BOOL)webView: shouldStartLoadWithRequest: navigationType:
will be called:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSArray * compenents = [request.URL.absoluteString componentsSeparatedByString:@"#"];
if (compenents.count > 1) {
NSString * cmd = compenents[1];
if ([cmd rangeOfString:@"cmd_alertMessage"].location != NSNotFound) {
// Call your obj-c method and get appropriated param
NSString * jsFunction = [NSString stringWithFormat:@"setParameterFromAAAMethod('%@')", [self aaa]];
// Return this param back to js
NSString * alertMessage = [webView stringByEvaluatingJavaScriptFromString:jsFunction];
}
}
return YES;
}
- (NSString *)aaa
{
return @"This is special parameter that you need";
}
So, it will work in 3 steps:
- Invoke hash changing to request parameter from obj-c code in
js; - Handle hash changed (parameter request) and return in back to
js in obj-c; - Handle recieved parameter in js again
Trigger objective C method from javascript using JavaScriptCore in iOS 7 in ViewControllers
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:you@site.com\"> <!-- 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.
Send javascript function to objective-C using JavascriptCore
Please mark Tayschrenn's answer as correct. I don't know how he knew this or where it's documented, but this is what I figured out by trial and error:
- (void)newWithFunc: (JSValue*)func
{
[func callWithArguments:@[]]; // will invoke js func with no params
}
Declaring the parameter (id)func
apparently causes the javascript-cocoa bridge to convert it to an NSDictionary (as you noticed), rendering it unusable as a callable JSValue.
Invoke method in objective c code from HTML code using UIWebView
To call method of Objective-C in JS:
the below url helps in doing that
How to invoke Objective C method from Javascript and send back data to Javascript in iOS?
There is no way of executing, we make workaround by a navigation to other page and during the navigation, a webview delegate will watch for prefix of the navigation and execute the method we specified.
sample.html
<html>
<head>
<script type='text/javascript'>
function getText()
{
return document.getElementById('txtinput').value;
}
function locationChange()
{
window.location = 'ios:webToNativeCall';
}
</script>
</head>
<body style='overflow-x:hidden;overflow-y:hidden;'>
<h2 style = "font-size:16px;" align='center'>Hi Welcome to Webpage</h2>
<br/>
<p align='center' style = "font-size:12px;">Please Click the button after entering the text</p>
<br/>
<center>
<input type='text' style='width:90px;height:30px;' id='txtinput'/>
</center>
<br/>
<center>
<input type='button' style='width:90px;height:30px;' onclick='locationChange()' value='click me'>
</center>
</body>
</html>
Objective-C code
when you click button in html page the below delegate will fired and navigation cancelled because we return NO and respective method is called
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([[[request URL] absoluteString] hasPrefix:@"ios:"]) {
// Call the given selector
[self performSelector:@selector(webToNativeCall)];
// Cancel the location change
return NO;
}
return YES;
}
- (void)webToNativeCall
{
NSString *returnvalue = [self.webviewForHtml stringByEvaluatingJavaScriptFromString:@"getText()"];
self.valueFromBrowser.text = [NSString stringWithFormat:@"From browser : %@", returnvalue ];
}
Related Topics
How Does Access-Control-Allow-Origin Header Work
Prototypical Inheritance - Writing Up
Css Transition Doesn't Work If Element Start Hidden
Force "Landscape" Orientation Mode
How to Include a JavaScript File in Another JavaScript File
From an Array of Objects, Extract Value of a Property as Array
Dynamically Access Object Property Using Variable
Using Async/Await With a Foreach Loop
How to Remove CSS Property Using JavaScript
How to Invoke Objective-C Method from JavaScript and Send Back Data to JavaScript in Ios
Var Functionname = Function() {} VS Function Functionname() {}
"Thinking in Angularjs" If I Have a Jquery Background
Does JavaScript Have Something Like Ruby'S Method_Missing Feature
JavaScript Array: Get "Range" of Items