美文网首页
iOS开发---OC和JS交互一:UIWebView和OC的交互

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

作者: 那时J花开 | 来源:发表于2019-04-08 17:39 被阅读0次

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

    一、协议拦截

    (1)、JS调用OC:
    • 逻辑: 点击JS中的button, 传递一个参数给OC, OC将传递来的参数打印出来

    • 实现:


      JS调用OC
    • JS中代码:

    <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;
            }
    
    • OC代码:
    //!< 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;
    }
    
    • 原理:
      • iOS与JS定义好jsToOC协议, 用作JS调用OC时的url.scheme;
      • JS在按钮点击后加载URL:jsToOc://loginSucceed?js_tokenString;
      • UIWebView会在每次loadRequest时, 调用回调:- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
      • 判断request的url.scheme是否是定义好的协议
    (2)、OC调用JS
    • 逻辑: 点击native的登录按钮, 将登录成功后的数据回传给JS

    • 实现: OC调用JS代码, 并传递参数
    • 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;
            }
    
    • OC代码:
    #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')"]];
        });
    }
    
    • 原理:
      • iOS与JS约定好OCTransferJS方法, 用作入口方法
      • OC 使用 - stringByEvaluatingJavaScriptFromString方法访问JS
      • OC在登录成功后将参数回传给JS

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

    二、使用JavaScriptCore框架

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

    (1)、JS调用OC
    • 逻辑: 点击HTML中的登录按钮, 传递登录成功的参数给OC, OC展示登录成功的参数

    • 实现: JS登录成功将参数传递给OC.gif
    • JS代码:

    <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;
            }
    
    • OC代码:
    #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];
        };
    }
    
    • 原理:
      • JS和OC定义好一个入口函数JSTransferOC
      • OC通过JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; 获取JSContext上下文
      • JS调用JSTransferOC函数, OC以Block形式监听此方法, context[@"JSTransferOC"] = ^() {}, 收到JS的参数回调

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

    (2)、OC调用JS
    • 逻辑:点击原生的登录按钮, 将登录成功的数据传递给JS, JS进行展示

    • 实现: OC调用JS
    • 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;
            }
    
    • OC代码:
    #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')"]];
        });
    }
    
    • 原理:
      • JS和iOS预定好入口函数:OCTransferJS
      • OC通过JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];方式来获取JS上下文JSContext;
      • JSContext通过evaluateScript函数执行JS代码OCTransferJS('login_success', 'login_success_token');

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

    三、JSExport协议

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

    (1)、JS调用OC
    • 逻辑: JS中点击按钮, 调用OC对象中的方法, 并传递参数.

    • 实现: JS调用OC本地代码,并传递参数
    • JS代码:

    <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);
            }
    
    • OC代码:
    //!< 定义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];
    }
    
    • 原理:
      • JS和OC定义好入口函数onClickiOSDelgate类;
      • OC创建遵循JSExport协议的JSObjectDelegate协议, 并在协议中声明- onClick: param2:方法;
      • JSExportAs() 将OC方法重命名, 以便JS能够识别;
      • OC类遵循JSExport协议, 并实现协议方法- onClick: param2:;
      • JS调用入口函数onClick, 并传递参数;
      • OC获取参数, 经过处理, 再次回传参数给JS, JS进行展示;

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

    (2)、OC调用JS:

    与上述方法一致.

    相关文章

      网友评论

          本文标题:iOS开发---OC和JS交互一:UIWebView和OC的交互

          本文链接:https://www.haomeiwen.com/subject/xgjpiqtx.html