iOS开发---OC和JS交互一:UIWebView和OC的交互

2019-04-08  本文已影响0人  那时J花开

iOS发展至今, 各种hybird方案层出不穷. 最经典的hybird方案莫过于在native框架中, 使用UIWebView来展示HTML页面. 那么我们需要去考虑怎么与web前端进行交互. 最近项目中有与web前端进行交互, 遇到了一些坑点, 故而在这里整理下UIWebView和iOS进行交互的方案.

一、协议拦截

(1)、JS调用OC:
<div style="height: 200px">
        <button onclick = "clickAction0(1111111)" style = "font-size: 40px; width: 400px; height: 100px" >
            button
        </button>
    </div>
function clickAction0(clickId) {
            var token = "js_tokenString";
            loginSucceed(token);
        }
function loginSucceed(token) {
            var action = "loginSucceed";
            jsToOc(action, token);
        }
function jsToOc(action, params) {
            var url = "jsToOc://" + action + "?" + params;
            loadURL(url);
        }
function loadURL(url) {
            window.location.href = url;
        }
//!< uwebView每次加载请求前都会调用这个方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    
    //!< 通过判断URL.scheme来判断是否是JS中定义好的方法
    if ([request.URL.scheme caseInsensitiveCompare:@"jsToOc"] == NSOrderedSame) {
        [self presentAlertWithTitle:request.URL.host message:request.URL.query handler:nil];
        return NO;
    }
    return YES;
}
(2)、OC调用JS
<div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 100px;">
        
    </div>
function OCTransferJS(action, params) {
<!--            alert('JS代码被OC调用了' + action + params);-->
            document.getElementById("returnValue").innerHTML = 'JS代码被OC调用, 并向JS传递了参数' + action + '?' + params;
        }
#pragma mark - OC调用JS  OC调用JS代码, 并传递参数
//!< 点击native按钮登录, 并将登录成功的参数回传给JS
- (void)login:(UIButton *)sender {
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSString *jsString = [NSString stringWithFormat:@"OCTransferJS('loginSucceed', 'oc_tokenString')"];
        [self.webView stringByEvaluatingJavaScriptFromString:jsString];
//         JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//        [context evaluateScript:[NSString stringWithFormat:@"OCTransferJS('login_success', 'login_success_token')"]];
    });
}

- stringByEvaluatingJavaScriptFromString 只有在整个webViwe加载完毕后才会执行

二、使用JavaScriptCore框架

通过协议拦截的方式, 可以进行OC与JS之间的交互, 但是太过繁琐了.
Apple在iOS7中提供了一个功能强大的框架:JavaScriptCore, 简化了UIWebView与OC的交互

(1)、JS调用OC
<button onclick = "login()" style = "font-size: 40px;">登录</button>
function login() {
            var token = "js_tokenString";
            loginSucceed(token);
        }
//!< 登录成功
        function loginSucceed(token) {
            var action = "loginSucceed";
            JSTransferOC(action, token);
        }
    
        //!< JS调用OC入口
        function JSTransferOC(action, params) {
            var url = "JSTransferOC://" + action + "?" + params;
            loadURL(url);
        }
    
        //!< 加载URL
        function loadURL(url) {
            window.location.href = url;
        }
#import <JavaScriptCore/JavaScriptCore.h>
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    context[@"JSTransferOC"] = ^(NSString *action, NSString *params) {
        [self presentAlertWithTitle:[NSString stringWithFormat:@"获取到点击js按钮的事件, 传递过来了事件:%@" , action] message:params handler:nil];
    };
}

Tips: 以Block形式监听函数时, 不要在Block内部使用JSValue或者JSContext, 因为JSContext强引用Block, Block强引用外部变量. JSValue需要JSContext来执行JS代码, 故而JSVaule又强引用JSContext, 会形成循环引用. 因为JS中没有弱引用的概念, 所以__weak没有作用. 可以用将JSValue作为Block内部变量的方式 和 [JSContext currentContext]的方式来解决两种循环引用

(2)、OC调用JS
<div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 100px;">
    </div>
function OCTransferJS(action, params) {
            document.getElementById("returnValue").innerHTML = 'JS代码被OC调用, 并向JS传递了参数' + action + '?' + params;
        }
#pragma mark - OC调用JS  OC调用JS代码, 并传递参数
//!< 点击native按钮登录, 并将登录成功的参数回传给JS
- (void)login:(UIButton *)sender {
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //!< 方式二: 使用JavaScriptCore
         JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        [context evaluateScript:[NSString stringWithFormat:@"OCTransferJS('login_success', 'login_success_token')"]];
    });
}

PS: 笔者在项目中遇到过传递对象的情况,将NSDictionary转换成JSON字符串包装在参数中, 类似这种[NSString stringWithFormat:@"OCTransferJS('%@')", resustJsonStr], 不过不能成功传递. 推测可能是这样调用的话, JS不能识别, 还望有知道答案的大牛不吝赐教~

三、JSExport协议

JSExportJavaScriptCore框架中的一个协议. 如果一个对象遵循了JSExport协议, 那么该对象的方法会对JS开放, 允许JS直接调用;

(1)、JS调用OC
<div style="height: 200px; margin-top: 20px">
        <button onclick = "clickbtn()" style = "font-size: 40px; width: 400px; height: 100px" >
            js调用OC代码
        </button>
    </div>
function clickbtn() {
            var str = window.iOSDelgate.onClick("点击了button", "调用成功");
            alert(str);
        }
//!< 定义protocol

@protocol JSObjectDelegate <JSExport>

//!< 取一个JS能够识别的名字
JSExportAs(onClick, - (NSString *)onClick:(NSString *)param1 param2:(NSString *)param2);

@end
//!< 遵循代理 让JS能够调用到方法
@interface UIWebViewVC : UIViewController<JSObjectDelegate, UIWebViewDelegate>

@end
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    //!< 将JS和OC中定义好的类(iOSDelgate)指向自身, 这样JS中的 window.iOSDelgate.onClick();就会调用oc代码
    context[@"iOSDelgate"] = self;
}
#pragma mark - 实现protocol方法, 供JS调用, JS调用OC本地代码, 常用在定位, 获取相册等操作
//!< 点击HTML中的按钮, 调用OC中的代码, 并回传参数给JS
- (NSString *)onClick:(NSString *)param1 param2:(NSString *)param2 {
    
    return [NSString stringWithFormat:@"OC方法被JS调用%@%@", param1, param2];
}

Tips: 若OC方法没有参数, 则不需要JSExportAs()进行重命名;

(2)、OC调用JS:

与上述方法一致.

上一篇 下一篇

猜你喜欢

热点阅读