• OC与JS交互之UIWebView


    一、UIWebView介绍

    (1)UIWebView提供了三个方法来加载html资源

    1、 loadHTMLString:baseURL: 把html文件的内容以字符串的形式加载到webView里面,然后解析。编码为UTF8

    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
    NSURL *baseURL = [[NSBundle mainBundle] bundleURL];
    [self.webView loadHTMLString:[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil] baseURL:baseURL];

    2、loadData:MIMEType:textEncodingName:baseURL: 把html文件的内容以二进制的形式加载到webView里面,然后解析。MIMETYPE指定html文件的格式为"text/html",编码为UTF8

    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
    NSURL *baseURL = [[NSBundle mainBundle] bundleURL];
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    [self.webView loadData:data MIMEType:@"text/html" textEncodingName:@"UTF-8" baseURL:baseURL];

    3、loadRequest:构造一个请求,webView以请求的形式加载本地html文件,然后解析。这种方式可以直接加载网络html

    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    NSURL *url = [NSURL fileURLWithPath:filePath];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];

    (2)UIWebView代理

    //成为UIWebView的代理,遵守UIWebViewDelegate协议,就能监听UIWebView的加载过程
    
    //UIWebView在发送请求之前,都会调用这个方法,如果返回NO,代表停止加载请求,返回YES,代表允许加载请求
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
    
    //开始发送请求(加载数据)时调用这个方法
    - (void)webViewDidStartLoad:(UIWebView *)webView;
    
    //请求完毕(加载数据完毕)时调用这个方法
    - (void)webViewDidFinishLoad:(UIWebView *)webView;
    
    //请求错误时调用这个方法
    - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;

    (3)常用属性和方法

    // 刷新
    - (void)reload;
    // 停止加载
    - (void)stopLoading;
    // 后退函数
    - (void)goBack;
    // 前进函数
    - (void)goForward;
    // 是否可以后退
    @property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack;
    // 是否可以向前
    @property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward;
    // 是否正在加载
    @property (nonatomic, readonly, getter=isLoading) BOOL loading;

    二、基于UIWebView"协议拦截"实现的交互方式

    (1)JS调用OC

    实现原理

    1、JS与iOS约定好jsToOc协议,用作JS在调用iOS时url的scheme;
    2、点击JS中的按钮加载含有token数据的url:(jsToOc://loginSucceed?js_tokenString);
    3、iOS的UIWebView在加载请求前都会调用-webView:shouldStartLoadWithRequest:navigationType:方法来确认是否加载此请求;
    4、iOS在此方法内截取jsToOc协议获取JS传过来的数据,执行OC相应的方法,并不允许加载此请求。

     JS代码:

            var loginBtn = document.getElementById("loginID");
            loginBtn.onclick = function () {
                var token = "js_tokenString";
                loginSuccess(token);
            }
    
            function loginSuccess(token) {
                var action = "loginSuccess";
                jsToOc(action, token);
            }
    
            //JS调用OC入口
            function jsToOc(action, params) {
                var url = "jsToOc://" + action + "?" + params;
                window.location.href = url;
    
            } 

    OC代码:

    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
        NSString *absolutePath = request.URL.absoluteString;
        NSString *scheme = request.URL.scheme;
        NSString *host = request.URL.host;
        NSString *query = request.URL.query;
        
        if ([scheme caseInsensitiveCompare:@"jsToOc"] == NSOrderedSame) {
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"JS调OC01" message:query delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
            return NO;
        }
        return YES;
    }

    除了显示截取到的数据,iOS还可以将request.URL.host看作JS想调用的方法名,将request.URL.query看作该方法的参数集,从而体现出JS调用iOS的概念。

    JS代码:

            function btnClick1() {
                 window.location.href = "rrcc://showMobile"
            }
            function btnClick2() {
                 window.location.href = "rrcc://showName_?xiaohuang"
            }
            function btnClick3() {
                 window.location.href = "rrcc://showSendNumber_msg_?13300001111&go climbing this weekend"
            }    

    OC代码:

    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
        NSString *absolutePath = request.URL.absoluteString;
        NSString *scheme = request.URL.scheme;
        NSString *host = request.URL.host;
        NSString *query = request.URL.query;
        
       if ([scheme caseInsensitiveCompare:@"rrcc"] == NSOrderedSame){
            NSString *schemeStr = @"rrcc://";
            NSString *subPath = [absolutePath substringFromIndex:schemeStr.length];
            if ([subPath containsString:@"?"]) {//1个或多个参数
                if ([subPath containsString:@"&"]) {//多个参数
                    NSArray *components = [subPath componentsSeparatedByString:@"?"];
                    NSString *methodName = [components firstObject];
                    methodName = [methodName stringByReplacingOccurrencesOfString:@"_" withString:@":"];
                    SEL sel = NSSelectorFromString(methodName);
                    NSString *parameter = [components lastObject];
                    NSArray *params = [parameter componentsSeparatedByString:@"&"];
                    if (params.count == 2) {
                        if ([self respondsToSelector:sel]) {
                            [self performSelector:sel withObject:[params firstObject] withObject:[params lastObject]];
                        }
                    }
                }else{//1个参数
                    NSArray *components = [subPath componentsSeparatedByString:@"?"];
                    
                    NSString *methodName = [components firstObject];
                    methodName = [methodName stringByReplacingOccurrencesOfString:@"_" withString:@":"];
                    SEL sel = NSSelectorFromString(methodName);
                    NSString *parameter = [components lastObject];
                    if ([self respondsToSelector:sel]) {
                        [self performSelector:sel withObject:parameter];
                    }
                }
            }else{//没有参数
                NSString *methodName = [subPath stringByReplacingOccurrencesOfString:@"_" withString:@":"];
                SEL sel = NSSelectorFromString(methodName);
                
                if ([self respondsToSelector:sel]) {
                    [self performSelector:sel];
                }
            }
            return NO;
        }
        return YES;
    }
    
    
    - (void)showMobile{
        NSLog(@"showMobile");
    }
    - (void)showName:(NSString *)name{
        NSLog(@"showName:%@", name);
       
    }
    
    - (void)showSendNumber:(NSString *)phone msg:(NSString *)msg{
        NSLog(@"showSendNumbermsg:==phone:%@===msg:%@", phone, msg);
    }

    (2)OC调用JS

    OC使用UIWebView的-stringByEvaluatingJavaScriptFromString:方法访问JS;

    JS代码:

            function alertMobile() {
                alert('alertMobile 我是手机号是:13300001111')
            }
        
            function alertName(msg) {
                alert('alertName 你好 ' + msg + ', 我也很高兴见到你')
            }    

    OC代码:

    - (void)btnAction:(UIButton *)sender{
        if (sender.tag == 123) {
            [self.webView stringByEvaluatingJavaScriptFromString:@"alertMobile()"];
        }
        
        if (sender.tag == 234) {
            [self.webView stringByEvaluatingJavaScriptFromString:@"alertName('小红')"];
        }
    }

    UIWebView的-stringByEvaluatingJavaScriptFromString:方法可以执行JS代码。但只有在整个webView加载完成之后调用此方法才会有响应。

    三、OC与JS交互之JavaScriptCore

    JavaScriptCore提供了JavaScript和Objective-C桥接的Obj-C API。JavaScriptCore提供了让我们脱离UIWebView执行JavaScript脚本的能力,以及使用现代的Objective-C语法(例如Blocks和下标)在Objective-C和JavaScript之间无缝的传递值或者对象。

    (1)JS调用OC

    原理:

    1、JS与iOS约定好jsToOc方法,作为JS调用iOS的入口;
    2、iOS通过[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]获取JS代码的上下文JSContext;
    3、JS调用jsToOc方法,iOS使用Block形式监听(重写)此方法context[@"jsToOc"] = ^() {},收到JS的调用请求和参数;

    JS代码:

            var loginBtn = document.getElementById("loginID");
            loginBtn.onclick = function () {
                var token = "js_tokenString";
                loginSuccess(token);
            }
    
            //登录成功
            function loginSuccess(token) {
                var action = "loginSuccess";
                jsToOc(action, token);
    
            }
    
            //JS调用OC入口
            function jsToOc(action, params) {
                var url = "jsToOc://" + action + "?" + params;
                window.location.href = url;
            }
    function btnClick1() {
    //showMobile(); window.location.href = "rrcc://showMobile"; } function btnClick2() { showName('xiaohuang') } function btnClick3() { showSendMsg('13300001111', 'Go Climbing This Weekend !!!') }

    OC代码:

    #import <JavaScriptCore/JavaScriptCore.h>
    //UIWebView在每次加载请求完成后会调用此方法
    - (void)webViewDidFinishLoad:(UIWebView *)webView{
        
        //获取JS代码的执行环境/上下文/作用域
        JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        //监听JS代码里面的方法(执行效果上可以理解成重写了JS的方法)
        context[@"jsToOc"] = ^(NSString *action, NSString *param){
            dispatch_async(dispatch_get_main_queue(), ^{
                UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:action message:param delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
                [alertView show];
            });
        };
        
        context[@"btnClick1"] = ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"showMobile" message:@"showMobile" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
                [alertView show];
            });
        };
        
    //    context[@"showName"] = ^(NSString *name){
    //        dispatch_async(dispatch_get_main_queue(), ^{
    //            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"showName" message:name delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
    //            [alertView show];
    //        });
    //    };
        
        void(^showName)(NSString *name) = ^(NSString *name){
                dispatch_async(dispatch_get_main_queue(), ^{
                    UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"showName" message:name delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
                    [alertView show];
                });
        };
        [context setObject:showName forKeyedSubscript:@"showName"];
        
        context[@"showSendMsg"] = ^(NSString *phone, NSString *msg){
            dispatch_async(dispatch_get_main_queue(), ^{
                UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:phone message:msg delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
                [alertView show];
            });
        };
    }

    (2)OC调JS

    JS代码:

            function ocToJs(action, params) {
                document.getElementById("returnValue").innerHTML = action + '?' + params;
            }
        
            function alertMobile() {
                alert('alertMobile 我是手机号是:13300001111')
            }
        
            function alertName(msg) {
                alert('alertName 你好 ' + msg + ', 我也很高兴见到你')
            }
        
            function alertSendMsg(num,msg) {
                alert('alertSendMsg这是我的手机号:' + num + ',' + msg + '!!')
            }    

    OC代码:

    #pragma mark - Action
    - (void)loginBtn01Action:(UIButton *)btn{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
            
            [context evaluateScript:[NSString stringWithFormat:@"ocToJs('loginSucceed', 'oc_tokenString')"]];
            
        });
    }
    
    - (void)btnAction:(UIButton *)sender{
        if (sender.tag == 123) {
            //[self.webView stringByEvaluatingJavaScriptFromString:@"alertMobile()"];
            JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
            [context evaluateScript:[NSString stringWithFormat:@"alertMobile()"]];
            
        }
        
        if (sender.tag == 234) {
            //[self.webView stringByEvaluatingJavaScriptFromString:@"alertName('小红')"];
            JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
            [context evaluateScript:[NSString stringWithFormat:@"alertName('小红')"]];
        }
        
        if (sender.tag == 345) {
            //[self.webView stringByEvaluatingJavaScriptFromString:@"alertSendMsg('18870707070','周末爬山真是件愉快的事情')"];
            JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
            [context evaluateScript:[NSString stringWithFormat:@"alertSendMsg('18870707070','周末爬山真是件愉快的事情')"]];
        }
    }

    除了使用JSContext的-evaluateScript:方法之外,还可以先通过[context[@"ocToJs"]获取到JS的ocToJs方法对应的JSValue,然后使用JSValue的-callWithArguments:方法调用JS的ocToJs方法。

    四、OC与JS交互之JSExport协议

    下面介绍如何使用JSExport协议在 UIWebView上实现OC与JS交互。JSExport是JavaScriptCore框架里的一个协议。如果一个协议遵守了JSExport,那么该协议的方法会对JS开放,允许JS直接调用。

    JS调OC

    实现原理:

    1、JS与iOS约定好OCJSBridge类名和jsToOc方法,作为JS调用iOS的入口OCJSBridge.jsToOc;
    2、iOS创建遵守JSExport协议的OCJSExport协议,在该协议中声明-jsToOc:params:方法(起个别名jsToOc);
    3、UIWebViewJSExportController遵守OCJSExport协议,实现该协议中的-jsToOc:params:方法;
    4、iOS通过[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]获取JS代码的上下文JSContext;
    5、iOS通过context[@"OCJSBridge"] = self;在context注册OCJSBridge对象为self(UIWebViewJSExportController的实例);
    6、JS调用OCJSBridge.jsToOc方法,iOSUIWebViewJSExportController的-jsToOc:params:方法会响应JS的调用请求和参数;

    JS代码:

            var loginBtn = document.getElementById("loginID");
            loginBtn.onclick = function () {
                var token = "js_tokenString";
                loginSuccess(token);
            }
    
            //登录成功
            function loginSuccess(token) {
                var action = "loginSuccess";
                OCJSBridge.jsToOc(action, token);
            }
           
        
            function btnClick1() {
                OCJSBridge.showMobile();
            }
        
            function btnClick2() {
                OCJSBridge.showName('xiaohuang');
            }
        
            function btnClick3() {
                OCJSBridge.showSendMsg('13300001111', 'Go Climbing This Weekend !!!');
            }
            

    OC代码

    #import <JavaScriptCore/JavaScriptCore.h>

     

    @protocol OCJSExport <JSExport>

     

    //为OC的-jsToOC:params:方法起个JS认识的别名jsToOc

    JSExportAs(jsToOc, - (void)jsToOc:(NSString *)action params:(NSString *)params);

    JSExportAs(showSendMsg, - (void)showSendMsg:(NSString *)phone params:(NSString *)msg);

    JSExportAs(showName, - (void)showName:(NSString *)name);

    //无参数和单参数时可不起别名

    - (void)showMobile;

     

    @end

    //UIWebView在每次加载请求完成后会调用此方法
    - (void)webViewDidFinishLoad:(UIWebView *)webView{
        
        //获取JS代码的执行环境/上下文/作用域
        JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        //! 在context注册OCJSBridge对象为self
        context[@"OCJSBridge"] = self;//!< 有循环引用问题
        
    }
    //! 实现OCJSExport协议的方法
    - (void)jsToOc:(NSString *)action params:(NSString *)params {
        
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:action message:params delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
        });
    }
    
    - (void)showMobile{
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"showMobile" message:@"showMobile" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
        });
    }
    
    - (void)showName:(NSString *)name{
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"提示" message:name delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
        });
    }
    
    - (void)showSendMsg:(NSString *)phone params:(NSString *)msg{
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:phone message:msg delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
        });
    }

    1、JS的方法命名规则与OC不一样,当OCJSExport协议中的方法有多个参数时,需要使用JSExportAs(<#PropertyName#>, <#Selector#>)为OC方法起个别名。否则,JS就需要很别扭的使用OCJSBridge.jsToOcParams来调用OC的-jsToOc:params:方法(无参数和单参数时可不起别名)。
    2、context[@"OCJSBridge"] = self;会有循环引用问题,导致self的-dealloc方法不被执行。因为JS中没有弱引用,所以__weak在这里不起作用。一般来说,可以使用单独的类来处理OCJSExport协议的相关方法,以解决此问题(比如:context[@"OCJSBridge"] = [OCJSBridge new];)。

    参考:https://www.jianshu.com/p/f94448235209

       https://www.cnblogs.com/markstray/p/5757238.html

       https://www.jianshu.com/p/f4ec947f0721

       https://www.cnblogs.com/markstray/p/5757255.html

    GitHub:https://github.com/hongsheng1024/RunTime

  • 相关阅读:
    [Swift]LeetCode462. 最少移动次数使数组元素相等 II | Minimum Moves to Equal Array Elements II
    [Swift]LeetCode458. 可怜的小猪 | Poor Pigs
    [Swift]数组排序:sort和sorted
    2013年总结(0)-序
    2013年总结(0)-序
    互联网日志的种类、存储和计算
    互联网日志的种类、存储和计算
    第一次当面试官
    第一次当面试官
    JS实践与写博客-序
  • 原文地址:https://www.cnblogs.com/whongs/p/9880283.html
Copyright © 2020-2023  润新知