Valerio Santinelli
Valerio Santinelli

Reputation: 1702

Returning values to JavaScript when calling an Objective-C function

I've been using WebViewJavascriptBridge to bridge Objective-C and JavaScript in an iOS application. It works great but it lacks support for returning values to the calling JavaScript code from the called native Objective-C function.

I'm pretty sure that Cordova (PhoneGap) does that in some way with callbacks but I've been having a hard time trying to understand how the underlying mechanics work.

Is there anyone who's had the same problem and that managed to find a solution?

Upvotes: 3

Views: 3902

Answers (3)

housenkui
housenkui

Reputation: 31

WebViewJavascriptBridgehave no maintain for a long time.

Maybe you should change to use this library.

SDBridgeOC

 self.bridge.consolePipeBlock = ^(id  _Nonnull water) {
        NSLog(@"Next line is javascript console.log------>>>>");
        NSLog(@"%@",water);
    };

This can easy to get javascript console.log.

Also have Swift language Version.

SDBridgeSwift

bridge.consolePipeClosure = { water in
         guard let jsConsoleLog = water else {
             print("Javascript console.log give native is nil!")
              return
         }
         print("Next line is Javascript console.log----->>>>>>>")
         print(jsConsoleLog)
      }

Also have h5 demo for your partner.

Upvotes: 0

DrC
DrC

Reputation: 7698

If you mean, "return" in the sense that your JS caller will see the results as returned from the call, I don't know how to do it. I suspect it would take a level of thread manipulation not available in JS. Instead, you can recode your JS side logic to create a result handler function. In pseudo code:

res = CallObjC(args);
work with res...

Becomes

CallObjC(args, function(res) { work with res...});

Admittedly a bit awkward but get used to it - it is a very common pattern. Internally, the JS bridge code creates a mapping of the request to the callback function. When the ObjC code has the result, it uses WebView's stringByEvaluatingJavaScriptFromString to call the JS bridge code that locates and invokes the callback.

@Richard - Be a bit careful with the solution you posted. Setting window.location to invoke shouldStartLoadWithRequest can result in both lost functionality in the webview and also lost messages to ObjectiveC. Current practice is to use an iframe and have some kind of message queue that can buffer up messages.

Upvotes: 2

Richard J. Ross III
Richard J. Ross III

Reputation: 55553

Now, I've never used WebViewJavascriptBridge, but I have done this in objective-c using a simple delegate, so maybe this will help you:

MyScript.js

// requestFromObjc
// functionName (required):
//      the name of the function to pass along to objc
// returnedResult (not used by caller):
//      the result given by objc to be passed along
// callback (not used by caller):
//      sent by objc, name of the function to execute
function requestFromObjc(functionName, objcResult, callback)
{    
    if (!objcResult)
    {
        window.location = "myapp://objcRequest?function=" + functionName + "&callback=" + arguments.callee.name + "&callbackFunc=" + arguments.callee.caller.name;
    }
    else
    {
        window[callback](objcResult);
    }
}

function buttonClick(objcResult)
{    
    if (!objcResult)
    {
        // ask for the color from objc
        requestFromObjc("buttonColor&someParam=1");
    }
    else
    {
        // do something with result (in this case, set the background color of my div)
        var div = document.getElementById("someDiv");

        div.style.background = objcResult;
    }
}

MyPage.html

<html>
    <head>
        <script type="text/javascript" src="MyScript.js"></script>
    </head>
    <body>
        <div id="someDiv">
            This is my div! Do not make fun of it, though.
        </div>

        <button onClick="buttonClick(undefined)">
            Click Me!
        </button>
    </body>
</html>

ViewController.m

-(NSString *) javaScriptResultForRequest:(NSDictionary *) params
{
    if ([params[@"function"] isEqualToString:@"buttonColor"])
    {
        NSArray *colors = @[ @"red", @"yellow", @"blue", @"green", @"purple" ];
        return colors[arc4random_uniform(colors.count)];
    }
    else
    {
        return @"undefined";
    }
}

-(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    if ([[[request URL] scheme] isEqualToString:@"myapp"])
    {
        // parse the URL here
        NSURL *URL = [request URL];

        if ([URL.host isEqualToString:@"objcRequest"])
        {
            NSMutableDictionary *queryParams = [NSMutableDictionary dictionary];
            NSArray *split = [URL.query componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"&="]];

            for (int i = 0; i < split.count; i += 2)
            {
                queryParams[split[i]] = split[i + 1];
            }

            NSString *result = [self javaScriptResultForRequest:queryParams];
            NSString *jsRequest = [NSString stringWithFormat:@"%@(\"%@\", \"%@\", \"%@\")", queryParams[@"callback"], queryParams[@"function"], result, queryParams[@"callbackFunc"]];

            // now we send this to the target
            [self.webView stringByEvaluatingJavaScriptFromString:jsRequest];
            return NO;
        }
    }

    return YES;
}

Obviously this is much slower than trying to do the equivalent function in pure JS, because of all the loops it has to jump through. However, if there is something you need to use in your JS code that's in your ObjC code already, this may be for you.

Upvotes: 4

Related Questions