iOS JavaScript bridge

112,254

Solution 1

There are a few libraries, but I didn't used any of these in big projects, so you might want to try them out:

However, I think it's something simple enough that you might give it a try yourself. I personally did exactly this when I needed to do that. You might also create a simple library that suits your needs.

1. Execute JS methods from Objective-C

This is really just one line of code.

NSString *returnvalue = [webView stringByEvaluatingJavaScriptFromString:@"your javascript code string here"];

More details on the official UIWebView Documentation.

2. Execute Objective-C methods from JS

This is unfortunately slightly more complex, because there isn't the same windowScriptObject property (and class) that exists on Mac OSX allowing complete communication between the two.

However, you can easily call from javascript custom-made URLs, like:

window.location = yourscheme://callfunction/parameter1/parameter2?parameter3=value

And intercept it from Objective-C with this:

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
   NSURL *URL = [request URL]; 
   if ([[URL scheme] isEqualToString:@"yourscheme"]) {
       // parse the rest of the URL object and execute functions
   } 
}

This is not as clean as it should be (or by using windowScriptObject) but it works.

3. Listen to native JS events from Objective-C (for example DOM ready event)

From the above explanation, you see that if you want to do that, you have to create some JavaScript code, attach it to the event you want to monitor and call the correct window.location call to be then intercepted.

Again, not clean as it should be, but it works.

Solution 2

The suggested method of calling objective c from JS in the accepted answer isn't recommended. One example of problems: if you make two immediate consecutive calls one is ignored (you can't change location too quickly).

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.

Solution 3

Update: This has changed in iOS 8. My answer applies to previous versions.

An alternative, that may get you rejected from the app store, is to use WebScriptObject.

These APIs are public on OSX but are not on iOS.

You need to define interfaces to the internal classes.

@interface WebScriptObject: NSObject
@end

@interface WebView
- (WebScriptObject *)windowScriptObject;
@end

@interface UIWebDocumentView: UIView
- (WebView *)webView;
@end

You need to define your object that's going to serve as your WebScriptObject

@interface WebScriptBridge: NSObject
- (void)someEvent: (uint64_t)foo :(NSString *)bar;
- (void)testfoo;
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
+ (WebScriptBridge*)getWebScriptBridge;
@end

static WebScriptBridge *gWebScriptBridge = nil;

@implementation WebScriptBridge
- (void)someEvent: (uint64_t)foo :(NSString *)bar
{
    NSLog(bar);
}

-(void)testfoo {
    NSLog(@"testfoo!");
}

+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
{
    return NO;
}

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
{
    return NO;
}

+ (NSString *)webScriptNameForSelector:(SEL)sel
{
    // Naming rules can be found at: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/Protocols/WebScripting_Protocol/Reference/Reference.html
    if (sel == @selector(testfoo)) return @"testfoo";
    if (sel == @selector(someEvent::)) return @"someEvent";

    return nil;
}
+ (WebScriptBridge*)getWebScriptBridge {
    if (gWebScriptBridge == nil)
        gWebScriptBridge = [WebScriptBridge new];

    return gWebScriptBridge;
}
@end

Now set that an instance to your UIWebView

if ([uiWebView.subviews count] > 0) {
    UIView *scrollView = uiWebView.subviews[0];

    for (UIView *childView in scrollView.subviews) {
        if ([childView isKindOfClass:[UIWebDocumentView class]]) {
            UIWebDocumentView *documentView = (UIWebDocumentView *)childView;
            WebScriptObject *wso = documentView.webView.windowScriptObject;

            [wso setValue:[WebScriptBridge getWebScriptBridge] forKey:@"yourBridge"];
        }
    }
}

Now inside of your javascript you can call:

yourBridge.someEvent(100, "hello");
yourBridge.testfoo();

Solution 4

In iOS8 you can look at WKWebView instead of UIWebView. This has the following class: WKScriptMessageHandler: Provides a method for receiving messages from JavaScript running in a webpage.

Solution 5

This is possible with iOS7, checkout http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/

Share:
112,254

Related videos on Youtube

andr111
Author by

andr111

Updated on May 18, 2020

Comments

  • andr111
    andr111 about 4 years

    I'm working on an app where I'm going to use both HTML5 in UIWebView and native iOS framework together. I know that I can implement communication between JavaScript and Objective-C. Are there any libraries that simplify implementing this communication? I know that there are several libraries to create native iOS apps in HTML5 and javascript (for example AppMobi, PhoneGap), but I'm not sure if there is a library to help create native iOS apps with heavy JavaScript usage. I need to:

    1. Execute JS methods from Objective-C
    2. Execute Objective-C methods from JS
    3. Listen to native JS events from Objective-C (for example DOM ready event)
    • kostyl
      kostyl almost 9 years
      You can use WKWebView: call from javascript window.webkit.messageHandlers.{NAME}.postMessage(message) and then handle it with [WKUserContentController addScriptMessageHandler:name:] to call Objective-C from JS
  • Ross
    Ross about 12 years
    @hova, the is no Obj-C V8 bridge. V8 is only leveraged for Android.
  • fabb
    fabb over 11 years
    Hint: don't put underscores or similar in your scheme.
  • Sam
    Sam over 11 years
    does anybody still contribute to kirin? i don't see any activity the last few month...
  • Pizzaiola Gorgonzola
    Pizzaiola Gorgonzola almost 11 years
    and return a value when you are supposed to :)
  • Folletto
    Folletto about 10 years
    Not really: JavaScriptCore doesn't interact with UIWebViews. It's a great tool, but for a different class of problems. ;)
  • kevintcoughlin
    kevintcoughlin about 10 years
    You can also use a queue in JavaScript that iOS can fetch to avoid losing messages from .src changing too quickly. The implementation linked above, github.com/marcuswestin/WebViewJavascriptBridge, uses a queue approach.
  • malhal
    malhal over 9 years
    Yes it does: JSContext* context = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptC‌​ontext"]; But JavaScriptCore is too buggy at the moment to be of any real use.
  • iCaramba
    iCaramba over 9 years
    Another Hint: Load the URL in an iFrame to prevent flickering
  • Josh Kethepalli
    Josh Kethepalli over 9 years
    Ofcourse, but the method is not executing continuoulsy.. it's a bug in the framework which is provided by apple
  • JERC
    JERC about 9 years
    @Folleto Hi, you said "but I didn't used any of these in big projects", why not?? did you find a several issue on these libraries??? Thanks in advance.
  • Folletto
    Folletto over 8 years
    No issues. I simply didn't use them, so I can't comment on using them on larger projects. I wanted to clarify that. :)
  • CupawnTae
    CupawnTae over 7 years
    WKWebView has a lot of problems, including not handling cookies properly. Just a warning for anyone who comes here thinking it will solve all your problems - google around a bit before adopting it.
  • chandru
    chandru about 7 years
    My app is rejected in app store. The issue I received was "The app contains or inherits from non-public classes in AppName: UIWebDocumentView "
  • Anjali Bhimani
    Anjali Bhimani almost 7 years
    github.com/marcuswestin/WebViewJavascriptBridge : This demo is not working can you please help me?