iOS与HTML混编(端内JS与OC交互)基础

作者: oldSix_Zhu | 来源:发表于2017-01-14 12:45 被阅读641次

    iOS与HTML混编,最好的例子就是网易新闻详情页的实现,图片与文字可以根据服务器返回的数据随意排版,很方便
    本文主要讲WKWebView,与UIWebView和JavaScriptCore孰优孰劣我就不比较了,
    网易新闻页实现流程我就不重复写了,还有其他好的文章放在最后,
    但感觉大多说的不细,对没有web开发经验的来说有点难懂,所以本文会把我写的一些简单的js代码拿出来,对没有web基础的来说更加清晰

    本文的基础知识主要为:
    1.点击html按钮,oc的控制器dismiss掉
    2.点击oc按钮,js切换页面
    3.点击html按钮,js切换页面
    4.点击html的div标签,js调用oc来present一个新的controller,上面有个返回按钮,点击可以返回webView的控制器
    点击获取源代码

    效果图就不放了,创建几个文件把代码全部复制一下就可以看到效果了

    我是文件列表

    ViewController中有个按钮,点击即可跳转到WebVC.
    WebVC的.m文件,其中值得注意的是WKDelegate,是解决WKWebView循环引用问题的代理

    #import "WebVC.h"
    #import <WebKit/WebKit.h>
    #import "WKDelegateController.h"
    
    @interface WebVC ()<WKUIDelegate,WKNavigationDelegate,WKDelegate>
    
    @property (strong, nonatomic) WKWebView *webView;
    @property (strong, nonatomic) WKUserContentController *userContent;
    @property (weak, nonatomic) UIButton *backBtn;
    @end
    
    @implementation WebVC
    
    - (void)dealloc
    {
        NSLog(@"无循环引用");
        //这里需要注意,前面增加过的方法一定要remove掉。
        //addScriptMessageHandler要和removeScriptMessageHandlerForName配套出现,否则会造成内存泄漏。
        [self.userContent removeScriptMessageHandlerForName:@"ocMethod"];
        [self.userContent removeScriptMessageHandlerForName:@"presentMethod"];
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor lightGrayColor];
        
        //创建配置
        WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        //创建UserContentController(提供javaScript向webView发送消息的方法)
        self.userContent = [[WKUserContentController alloc] init];
    
        //添加消息处理,
        //注意: addScriptMessageHandler后面的参数指代的是需要遵守WKScriptMessageHandler协议,结束时需要移除
        //但无论在哪里移除都会发现dealloc并不会执行,这样肯定是不行的,会造成内存泄漏
        //试了下用weak指针还是不能释放,不知道是什么原因
        //因此参考百度上的写法是用一个新的controller来处理,新的controller再绕用delegate绕回来,即使没移除也会走dealloc了
        WKDelegateController * delegateController = [[WKDelegateController alloc]init];
        delegateController.delegate = self;
        [self.userContent addScriptMessageHandler:delegateController  name:@"ocMethod"];
        [self.userContent addScriptMessageHandler:delegateController  name:@"presentMethod"];
    
        //将UserContent设置到配置文件中
        config.userContentController = self.userContent;
        //配置webView
        self.webView = [[WKWebView alloc]initWithFrame:self.view.bounds configuration:config];
        self.webView.UIDelegate = self;
        self.webView.navigationDelegate = self;
        [self.view addSubview:self.webView];
        //加载本地html
        NSURL *url = [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"html"];
        NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
        [self.webView loadRequest:urlRequest];
        
        //添加一个返回按钮
        UIButton *backBtn = [[UIButton alloc]initWithFrame:CGRectMake(10, 35, 50, 50)];
        [backBtn setTitle:@"返回" forState:UIControlStateNormal];
        [backBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [backBtn addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
        self.backBtn = backBtn;
        [self.webView addSubview:backBtn];
    }
    
    //这里就是使用高端配置,js调用oc的处理地方。我们可以根据name和body,进行桥协议的处理。
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
    {
        NSString *messageName = message.name;
        if ([@"ocMethod" isEqualToString:messageName])
        {
            id messageBody = message.body;
            NSLog(@"%@",messageBody);
            //点击html按钮,让当前webView页面dismiss掉
            [self dismissViewControllerAnimated:YES completion:nil];
        }
        else if([@"presentMethod" isEqualToString:messageName])
        {
            id messageBody = message.body;
            NSLog(@"%@",messageBody);
            //弹出一个新的红色背景的控制器
            UIViewController *newVC = [[UIViewController alloc]init];
            newVC.view.backgroundColor = [UIColor redColor];
            
            //添加一个返回按钮,返回webView
            CGRect rect = CGRectMake(100,100,100,50);
            UIButton *button = [[UIButton alloc]initWithFrame:rect];
            [button setTitle:@"返回" forState:UIControlStateNormal];
            [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            button.titleLabel.font = [UIFont systemFontOfSize:20];
            [button addTarget:self action:@selector(dismissNewVC) forControlEvents:UIControlEventTouchUpInside];
            [newVC.view addSubview:button];
    
            [self presentViewController:newVC animated:YES completion:nil];
        }
    }
    
    //返回方法
    -(void)dismissNewVC
    {
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    //点击oc按钮,调用js方法
    -(void)back
    {
        //第一种:直接调用
        //无论web页面跳转多少次,只要按钮存在,js都可以生效
        [self.webView evaluateJavaScript:@"function sayHello(){     \
                                                alert('jack')     \
                                            }                       \
                                            sayHello()"
                       completionHandler:^(id _Nullable result, NSError * _Nullable error) {
            
        }];
        
        //第二种:直接调用html文件中的js代码
        //注意:这种方式只有在第一个web页面js才能生效,跳转到第二个web页面就无效了
        //因为页面跳转后,就不是我们引入的本地的html页面了,自然也就引入不了我们本地的js代码
        //不过也正常,我们一般只需要在第一个页面添加一个返回按钮,dismiss掉这个webView,其他的功能都可以用html的按钮实现
        [self.webView evaluateJavaScript:@"hello()"completionHandler:^(id _Nullable result, NSError * _Nullable error) {
            
        }];
        
        //第三种:调用html文件中引入的js文件的js代码
        //注意:js效果与第二种相同
        [self.webView evaluateJavaScript:@"back()"completionHandler:^(id _Nullable result, NSError * _Nullable error) {
            
        }];
    }
    
    //wkWebView直接调用js的弹窗是无效的,需要拦截js中的alert,用oc的方式展现出来
    //该方法中的message参数就是我们JS代码中alert函数里面的参数内容
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
    {
        NSLog(@"----------%@",message);
        
        UIAlertController *alertView = [UIAlertController alertControllerWithTitle:@"js的弹窗" message:message preferredStyle:UIAlertControllerStyleAlert];
        [alertView addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            //一定要调用下这个block,否则会崩
            //API说明:The completion handler to call after the alert panel has been dismissed
            completionHandler();
        }]];
        [self presentViewController:alertView animated:YES completion:nil];
    }
    
    @end
    

    WKDelegateController.h文件:

    #import <UIKit/UIKit.h>
    #import <WebKit/WebKit.h>
    @class WKDelegateController;
    
    @protocol WKDelegate <NSObject>
    
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
    
    @end
    
    @interface WKDelegateController : UIViewController <WKScriptMessageHandler>
    
    @property (weak , nonatomic) id<WKDelegate> delegate;
    
    @end
    

    WKDelegateController.m文件

    #import "WKDelegateController.h"
    
    @interface WKDelegateController ()
    
    @end
    
    @implementation WKDelegateController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
    }
    
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
    {
        if ([self.delegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)])
        {
            [self.delegate userContentController:userContentController didReceiveScriptMessage:message];
        }
    }
    

    index.html文件

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
                <title>js与oc互调</title>
                <link href="index.css" rel="stylesheet">
        </head>
        <body>
            <button onclick="clickBtn()"> 点击按钮跳转到baidu </button>
            <br>
            <button onclick="dismiss()"> 点击按钮dismiss回去 </button>
            <!--导航-->
            <div >
                <ul>
                    <div class="time"  onclick="clickTime()">点击我present一个新的controller</div>
                    <li><a href="https://www.baidu.com" >baidu</a></li>
                    <li><a href="#" >about</a></li>
                    <li><a href="#" >services</a></li>
                    <li><a href="#" >gallery</a></li>
                    <li><a href="#" >contact</a></li>
                </ul>
            </div>
    
            <!--引入的js函数文件-->
            <script src="index.js" type="text/javascript"></script>
            <!--直接写的js函数-->
            <script type="text/javascript">
                function hello(){
                    alert("你好!");
                }
            </script>
    
        </body>
    </html>
    

    index.js文件

    //确认js加载成功
    window.onload = function (){
        alert(0);
    }
    
    //点击html按钮,调用js方法
    function clickBtn(){
        window.location.href ="https://www.baidu.com";
    }
    
    //点击html按钮,调用oc方法
    function dismiss(){
        window.webkit.messageHandlers.ocMethod.postMessage("我点击html按钮,调用oc的方法,让webView消失");
    }
    
    //点击html的div标签,调用oc方法
    function clickTime(){
        window.webkit.messageHandlers.presentMethod.postMessage("我点击div标签,调用oc的方法,弹出一个控制器");
    }
    
    //点击oc按钮,要调用的js方法
    function back() {
        window.location.href = "https://www.baidu.com";
        
    }
    

    最后是无关紧要的index.css文件

    *{
        padding = 0;
        margin = 0;
    }
    
    body{
        padding:100px 0 0 0;
        text-align:center;
        background-color:lightgray;
        font-size:40px;
    }
    
    button{
        font-size:50px;
    }
    
    li{
        list-style-type:none
    }
    
    a{
        font-size:50px;
    }
    

    比较好的链接,也介绍了一些第三方框架:
    这个实现网易新闻详情页写的很详细,所以我就不写了.但是注意这里遗漏了解决循环引用问题,练习的时候要注意:<a href="http://www.jianshu.com/p/75f3abd40cc1">iOS-使用WKWebview实现新闻详情页</a>

    <a href="http://www.jianshu.com/p/1f2dc3d3090a">iOS WKWebView导致ViewController不调用dealloc方法</a>

    WKWebView并不会去NSHTTPCookieStorage中读取cookie,因此导致cookie丢失解决办法:<a href="http://www.jianshu.com/p/4fa8c4eb1316">IOS进阶之WKWebView</a>

    讲UIWebView与JavaScriptCore细的文章:
    <a href="http://www.jianshu.com/p/d19689e0ed83">iOS下JS与原生OC互相调用(总结)</a>
    <a href="http://liuyanwei.jumppo.com/2016/04/03/iOS-JavaScriptCore.html">iOS JavaScriptCore使用</a>
    <a href="http://www.jianshu.com/p/84a6b1ac974a">iOS H5容器的一些探究(一):UIWebView和WKWebView的比较和选择</a>

    相关文章

      网友评论

      本文标题:iOS与HTML混编(端内JS与OC交互)基础

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