How to call Objective-C from Javascript?
Asked Answered
R

8

42

I have a WebView, and I want to call a view in Objective-C from JavaScript. Does someone know how I can do this?


I have this code in my ViewController:

- (BOOL)webView:(UIWebView *)webView2 
 shouldStartLoadWithRequest:(NSURLRequest *)request 
 navigationType:(UIWebViewNavigationType)navigationType {

 NSString *requestString = [[request URL] absoluteString];
 NSArray *components = [requestString componentsSeparatedByString:@":"];

 if ([components count] > 1 && 
  [(NSString *)[components objectAtIndex:0] isEqualToString:@"myapp"]) {
  if([(NSString *)[components objectAtIndex:1] isEqualToString:@"myfunction"]) 
  {

   NSLog([components objectAtIndex:2]); [[Airship shared] displayStoreFront]; //<- This is the code to open the Store
   NSLog([components objectAtIndex:3]); // param2
   // Call your method in Objective-C method using the above...
  }
  return NO;
 }

 return YES; // Return YES to make sure regular navigation works as expected.
}

And in Javascript:

function store(event)
{
    document.location = "myapp:" + "myfunction:" + param1 + ":" + param2;
}

But nothing happens.

Ramshackle answered 2/11, 2009 at 17:4 Comment(3)
developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/… Have you tried this URL ?Coumarin
possible duplicate of How to invoke Objective C method from Javascript and send back data to Javascript in iOS?Backspace
a detailed example of Sixten Otto's response here: #21928112Disgust
I
52

The standard workaround for UIWebView is to set a UIWebViewDelegate, and implement the method webView:shouldStartLoadWithRequest:navigationType:. In your JavaScript code, navigate to some fake URL that encodes the information you want to pass to your app, like, say:

window.location = "fake://myApp/something_happened:param1:param2:param3";

In your delegate method, look for these fake URLs, extract the information you need, take whatever action is appropriate, and return NO to cancel the navigation. It's probably best if you defer any lengthy processing using some flavor of performSelector.

Incarnation answered 2/11, 2009 at 18:5 Comment(5)
The problem for this solution is that if "fake//..." fails to load, webview:shouldStartLoadWithRequest:navigationType: will not be called. Any way to fix it?Roommate
What would cause it to fail before webview:shouldStartLoadWithRequest:navigationType: is called? (The whole point of this callback is that it happens before the system begins any network loading, etc.)Incarnation
I recommend serializing the data you're passing in the URL as JSON, it makes things much more flexible.Backspace
You can just call the native methods from js by cordova.exec(function(success){ alert('success'); console.log(success); }, function(fail){ console.log(fail); }, '<ClassName>','<MethodName>',[]); and add this line to config.xml <feature name="<ClassName>”> <param name="ios-package" value="<ClassName>" /> </feature>Kelso
Nothing about the question presumes that the application is using Cordova, but if you are, and they provide a helper function for this, that's handy.Incarnation
F
32

The window.location method of calling objective c from JS isn't recommended. One example of problems: if you make two immediate consecutive calls one is ignored (since you can't change location too quickly) - try it yourself..

I recommend the following alternative approach:

function execute(url) 
{
  var iframe = document.createElement("IFRAME");
  iframe.setAttribute("src", url);
  document.documentElement.appendChild(iframe);
  iframe.parentNode.removeChild(iframe);
  iframe = null;
}

You call the execute function repeatedly and since each call executes in its own iframe, they should not be ignored when called quickly.

Credits to this guy.

Flocculent answered 1/11, 2012 at 11:55 Comment(1)
Why should we assign null to iframe at the last line? (It doesn't enforce garbage collection or give any other profit)Azine
M
4

Obliviux,

Your code seems to be perfect.

The reason for the problem is that you must have missed to map the delegate.

Either

  1. Connect the delegate of the webView to the file owner in the .xib file

or

  1. Use webView.delegate = self;

in your viewDidLoad.

Thanks

Mcgee answered 22/10, 2011 at 7:28 Comment(0)
F
3

Like people said here, you have to use the method webView:shouldStartLoadWithRequest:navigationType: from the UIWebviewDelegate.

This api http://code.google.com/p/jsbridge-to-cocoa/ does it for you. It is very lightweight. You can pass images, strings and arrays from javascript to objective-C.

Footcloth answered 20/9, 2010 at 3:16 Comment(1)
You may have to edit your answer, since its not possible to send images from javascript to objective c. The example that you have shown is just passing the id of the images from javascript to objective c by window.location way. Also, the images are in the local main bundle.Reinstate
N
2

I had an issue with this approach: I wanted to send several messages to the iphone device, but it seemed that they were "overlaped" as they could not process all of them sequentially.

Example: when executing this code:

window.location = "app://action/foo";
window.location = "app://action/bar";

The action foo was never executed.

What I had to do was the following:

waitingForMessage = false;

function MsgProcessed(){
    waitingForMessage = false;
}

function SyncLaunchURL(url){
    if (waitingForMessage){
        setTimeout(function(){SyncLaunchURL(url)},100);
    }else{
        window.location = url
        waitingForMessage = true;   
    }
}

SyncLaunchURL("app://action/foo");
SyncLaunchURL("app://action/bar");

With this approach, the iphone has to call MsgProcessed() after processing the call. This way works for me, and maybe helps someone with the same problem!

Nydianye answered 6/9, 2012 at 9:20 Comment(4)
it doesn't work if you just set window location asynchronously with setTimeout?Jasminejason
You still have the problem that if the window.location has not been processed it drops that "location".Nydianye
See my answer, it seems to solve this problem without polling or estimated delays.. The setTimeout solution isn't very elegant :)Flocculent
Please don't do this. Listen to @talkol.Egomania
C
1

Assuming you're doing an app, you can look at how PhoneGap implements that (or even use it). It's a library that supports back-and-forth communication between JS and OBJ-C. There are other libraries and solutions, as well.

If you're talking about a web app (something the user gets to from Mobile Safari), you can't get to Objective-C from there.

Circumscissile answered 2/11, 2009 at 17:52 Comment(0)
C
1

Check this one - understanding XMLHttpRequest responses using this (or other javascript) functions?, it's using objective C to call ajax js function, and get the response after it's done, you know the trick is that webview will be triggered when you change the location in javascript, so you can check the location to know its your javascript call or the real request.

Coating answered 8/5, 2012 at 13:0 Comment(0)
M
0

Although this is a very old question now, it keeps getting returned by Google and there is a good answer now: the WebScripting informal protocol. It allows you to expose an objective C object to Javascript.

http://developer.apple.com/library/safari/documentation/appleapplications/Conceptual/SafariJSProgTopics/Tasks/ObjCFromJavaScript.html#//apple_ref/doc/uid/30001215-BBCBFJCD

Microcosm answered 19/12, 2017 at 2:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.