美文网首页iOS Developer
iOS WebView 与JS的交互

iOS WebView 与JS的交互

作者: AryCode | 来源:发表于2016-10-08 18:02 被阅读2130次

    最近项目中需要使用JS与原生的交互,发现了不少的问题,做一个总结

    UIWebView

    • JS调用OC无参
    • JS调用OC有一个参数
    • JS调用OC有多个参数
    • OC调用JS并传值

    1.创建UIWebView

    - (UIWebView *)webView {
        if (!_webView) {
            _webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
            _webView.delegate = self;
            NSURL *localHTMLURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"UIWebViewJSInteraction" ofType:@"html"]];
            NSURLRequest *request = [NSURLRequest requestWithURL:localHTMLURL];
            [_webView loadRequest:request];
        }
        return _webView;
    }
    

    2.通过KVO获取JS上下文javaScriptContex

        @weakify(self)
        [[self.webView rac_valuesForKeyPath:@"documentView.webView.mainFrame.javaScriptContext" observer:self] subscribeNext:^(id x) {
            @strongify(self)
            JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
            YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
            [context setObject:proxy forKeyedSubscript:@"liqu_app"];
            self.context = context;
        }];
    

    注: A JSContext object represents a JavaScript execution environment. You create and use JavaScript contexts to evaluate JavaScript scripts from Objective-C or Swift code, to access values defined in or calculated in JavaScript, and to make native objects, methods, or functions accessible to JavaScript.
    译:大致的意思就是,通过这个对象我们可以访问JavaScript中的变量以及函数方法

    1. JS调用OC方法,是通过将对象注入到JSContext中,这个对象必须实现JSExport协议,这个协议定义了这个对象在JS环境中能够调用的方法
    @protocol  UIWebViewJSExport <JSExport>
    // 注意:此处一定不要写@optional,否则js不会回调协议里面的方法
    //@optional
    - (void)jsInvokeOCNoParm;
    - (void)jsInvokeOCWithParm1:(NSString *)parm1;
    - (void)jsInvokeOCWithParm1:(NSString *)parm1 parm2:(NSString *)parm2;
    - (NSString *)jsInvokeOCReturnValue;
    @end
    

    4 . JSOC相互交互代码

    // html 
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
                <title>JS调用OC方法</title>
                </head>
        <body>
            <input type="button" value="JS调用OC方法无参数" onclick="jsInvokeOCNoParm()" disable='true'/>
            <input type="button" value="JS调用OC方法有一个参数" onclick="jsInvokeOCWithParm1();"/>
            <input type="button" value="JS调用OC方法有两个参数" onclick="jsInvokeOCWithParm1Parm2();"/>
            <input type="button" value="OC调用JS方法有返回值" onclick="jsInvokeOCReturnValue();"/>
        </body>
        <script>
            function jsInvokeOCNoParm(){
                window.liqu_app.jsInvokeOCNoParm();
            }
        
            function jsInvokeOCWithParm1(){
                window.liqu_app.jsInvokeOCWithParm1('this is a parm');
            }
    
            function jsInvokeOCWithParm1Parm2(){
                var model = new Object();
                model.name = 'js';
                model.age = 18;
                window.liqu_app.jsInvokeOCWithParm1Parm2('this is a parm',JSON.stringify(model));
            }
        
            function jsInvokeOCReturnValue(){
                var str = window.liqu_app.jsInvokeOCReturnValue();
                var model = JSON.parse(str);
                alert('jsInvokeOCReturnValue___' + model.name);
            }
        
            <!--模拟打开App立即获取App内部信息的过程-->
            jsInvokeOCReturnValue();
            
            function ocCallJSMethod(str){
                var model = JSON.parse(str);
                alert('ocCallJSMethod_____' + model.name);
                return true;
            }
        </script>
        <style>
            input {
                display:block;
                margin:20px auto;
            }
        </style>
    </html>
    
    • JS调用OC的函数
    // oc
    #pragma mark -UIWebViewJSExport
    - (void)jsInvokeOCNoParm {
        NSLog(@"%@",NSStringFromSelector(_cmd));
    }
    
    - (void)jsInvokeOCWithParm1:(NSString *)parm1 {
        NSLog(@"%@,parm:%@",NSStringFromSelector(_cmd),parm1);
    }
    
    - (void)jsInvokeOCWithParm1:(NSString *)parm1 parm2:(NSString *)objStr {
        //objStr 从html传出对象到oc,这事一个字符串,需要我们转成oc对象
        NSData *data = [objStr dataUsingEncoding:NSUTF8StringEncoding];
        id obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        NSLog(@"%@,parm1:%@ parm2:%@",NSStringFromSelector(_cmd),parm1,obj);
    }
    
    // 从本地获取信息到HTML
    - (NSString *)jsInvokeOCReturnValue {
        UserInfo *info = [UserInfo new];
        info.name = @"liqu";
        info.age = 18;
        // 对象转字符串(实现Android和iOS逻辑统一)
        NSString *json = [info mj_JSONString];
        return json;
    }
    
    • OC调用JS的函数
    #pragma mark -oc调用js方法
    - (BOOL)ocCallJSMethod {
    
        // 方式1:
        UserInfo *info = [UserInfo new];
        info.name = @"liqu";
        info.age = 18;
        JSValue *jsResult = [self.context evaluateScript:[NSString stringWithFormat:@"ocCallJSMethod('%@')",[info mj_JSONString]]];
        BOOL result = [jsResult toBool];
        NSLog(@"%d",result);
        
        //方式2:
        {
            JSValue *ocInvokeJs = self.context[@"ocCallJSMethod"];
            JSValue *jsResult = [ocInvokeJs callWithArguments:@[[info mj_JSONString]]];
            BOOL result = [jsResult toBool];
            NSLog(@"%d",result);
        }
        return result;
    }
    

    WKWebView

    • 导入#import <WebKit/WebKit.h>
    • JS中注入模型对象
    • 实现WKWebView代理,判定引起这个回调的模型对象,执行相应代码
    1. 创建WKWebView
    - (WKWebView *)webView {
        if (!_webView) {
            WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
            configuration.userContentController = [[WKUserContentController alloc]init];
            YYWeakProxy <WKScriptMessageHandler>*proxy = (id<WKScriptMessageHandler>)[YYWeakProxy proxyWithTarget:self];
            [configuration.userContentController addScriptMessageHandler:proxy name:@"liqu_app"];
            _webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
            _webView.UIDelegate = self;
            NSURL *localHTMLURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"WKWebViewJSInteraction" ofType:@"html"]];
            NSURLRequest *request = [NSURLRequest requestWithURL:localHTMLURL];
            [_webView loadRequest:request];
        }
        return _webView;
    }
    

    注意:[config.userContentController addScriptMessageHandler:'会回调这个对象里面的方法' name:@"为这个对象打上一个标签,以便代理中进行区分"];这个方法中我们需要遵循WKScriptMessageHandler这个协议,这个协议只定义了一个方法,这个方法会在JS调用OC时会被调用

    2.HTML代码

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
                <title>JS调用OC方法</title>
                </head>
        <body>
            <input type="button" value="JS调用OC方法无参数,看日志" onclick="jsInvokeOCNoParm()" disable='true'/>
            <input type="button" value="OC调用JS方法有返回值" onclick="jsInvokeOCReturnValue();"/>
        </body>
        <script>
            function jsInvokeOCNoParm() {
                window.webkit.messageHandlers.liqu_app.postMessage('123');
            }
        
            function jsInvokeOCReturnValue(){
                window.webkit.messageHandlers.liqu_app.postMessage('fetchUserInfo');
            }
        
            <!--模拟打开App立即获取App内部信息的过程-->
            window.webkit.messageHandlers.liqu_app.postMessage('fetchUserInfo');
    
            function ocCallJSMethod(str){
                var model = JSON.parse(str);
                alert('ocCallJSMethod_____' + model.name);
                return true;
            }
        </script>
        <style>
            input {
                display:block;
                margin:20px auto;
                font-size:30px;
            }
        </style>
    </html>
    

    3.WKScriptMessageHandler协议

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
        
        // 根绝对应的name和body执行对应的逻辑
        NSLog(@"%@   ---    %@",message.body,message.name);
        
        // deal
        if ([message.body isEqualToString:@"fetchUserInfo"]) {
            [self ocCallJSMethod];
        }else {
            
        }
    }
    

    4 .OC调用JS函数,使用如下方法

    - (void)ocCallJSMethod {
        
        UserInfo *info = [UserInfo new];
        info.name = @"liqu";
        info.age = 18;
        NSString *jsMethod = [NSString stringWithFormat:@"ocCallJSMethod('%@')",[info mj_JSONString]];
        
        [_webView evaluateJavaScript:jsMethod
                   completionHandler:^(id _Nullable obj, NSError * _Nullable
                                       error)
        {
            NSLog(@"%@",obj);
        }];
    }
    

    注意:这里我们传值给JS上下文,都用的YYWeakProxy对象,这个对象是self的代理,可以这么理解,因为无论UIWebView的
    [context setObject:proxy forKeyedSubscript:@"liqu_app"];
    或者WKWebView的
    [configuration.userContentController addScriptMessageHandler:proxy name:@"liqu_app"];都会对这个proxy进行强引用,会导致循环引用,虽然使用YYWeakProxy能够避免循环引用,但是还是有内存泄漏,检测发现YYWeakProxy即使在控制器释放后,这个对象还是没有调用dealloc方法,即使使用weak弱引用YYWeakProxy,这个对象也无法释放,如果你知道怎么解决,希望能告知!
    最后附上代码
    项目地址

    相关文章

      网友评论

        本文标题:iOS WebView 与JS的交互

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