zoukankan      html  css  js  c++  java
  • IOS 进阶之 WKWebView

    前言


    Xcode8发布以后,编译器开始不支持IOS7,所以很多应用在适配IOS10之后都不在适配IOS7了,其中包括了很多大公司,网易新闻,滴滴出行等。因此,我们公司的应用也打算淘汰IOS7。

    支持到IOS8,第一个要改的自然是用WKWebView替换原来的UIWebView。WKWebView有很多明显优势:

    • 更多的支持HTML5的特性

    • 官方宣称的高达60fps的滚动刷新率以及内置手势

    • 将UIWebViewDelegate与UIWebView拆分成了14类与3个协议,以前很多不方便实现的功能得以实现。

      https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/ObjC_classic/index.html

    • Safari相同的JavaScript引擎

    • 占用更少的内存

    UIWebView

    WKWebView

    因此,使用WkWebview替换UIWebView还是很有必要的。

    基本使用方法


    WKWebView有两个delegate,WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要处理一些跳转、加载处理操作,WKUIDelegate主要处理JS脚本,确认框,警告框等。因此WKNavigationDelegate更加常用。

    比较常用的方法:

    #pragma mark - lifeCircle

    - (void)viewDidLoad {

        [super viewDidLoad];

        webView = [[WKWebView alloc]init];

        [self.view addSubview:webView];

        [webView mas_makeConstraints:^(MASConstraintMaker *make) {

            make.left.equalTo(self.view);

            make.right.equalTo(self.view);

            make.top.equalTo(self.view);

            make.bottom.equalTo(self.view);

        }];

        webView.UIDelegate = self;

        webView.navigationDelegate = self;

        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];

    }

    #pragma mark - WKNavigationDelegate

    // 页面开始加载时调用

    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{

    }

    // 当内容开始返回时调用

    - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{

    }

    // 页面加载完成之后调用

    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{

    }

    // 页面加载失败时调用

    - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{

    }

    // 接收到服务器跳转请求之后调用

    - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{

    }

    // 在收到响应后,决定是否跳转

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{

        NSLog(@"%@",navigationResponse.response.URL.absoluteString);

        //允许跳转

        decisionHandler(WKNavigationResponsePolicyAllow);

        //不允许跳转

        //decisionHandler(WKNavigationResponsePolicyCancel);

    }

    // 在发送请求之前,决定是否跳转

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{

         NSLog(@"%@",navigationAction.request.URL.absoluteString);

        //允许跳转

        decisionHandler(WKNavigationActionPolicyAllow);

        //不允许跳转

        //decisionHandler(WKNavigationActionPolicyCancel);

    }

    #pragma mark - WKUIDelegate

    // 创建一个新的WebView

    - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{

        return [[WKWebView alloc]init];

    }

    // 输入框

    - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{

        completionHandler(@"http");

    }

    // 确认框

    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{

        completionHandler(YES);

    }

    // 警告框

    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{

        NSLog(@"%@",message);

        completionHandler();

    }

    OC与JS交互


    WKWebview提供了API实现js交互 不需要借助JavaScriptCore或者webJavaScriptBridge。使用WKUserContentController实现js native交互。简单的说就是先注册约定好的方法,然后再调用。

    JS调用OC方法

    oc代码(有误,内存不释放):

    @interface ViewController (){

        WKWebView * webView;

        WKUserContentController* userContentController;

    }

    @end

    @implementation ViewController

    #pragma mark - lifeCircle

    - (void)viewDidLoad {

        [super viewDidLoad];

        //配置环境

        WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];

        userContentController =[[WKUserContentController alloc]init];

        configuration.userContentController = userContentController;

        webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration];

        //注册方法

        [userContentController addScriptMessageHandler:self  name:@"sayhello"];//注册一个name为sayhello的js方法

        [self.view addSubview:webView];

        [webView mas_makeConstraints:^(MASConstraintMaker *make) {

            make.left.equalTo(self.view);

            make.right.equalTo(self.view);

            make.top.equalTo(self.view);

            make.bottom.equalTo(self.view);

        }];

        webView.UIDelegate = self;

        webView.navigationDelegate = self;

        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]];

    }

    - (void)dealloc{

        //这里需要注意,前面增加过的方法一定要remove掉。

        [userContentController removeScriptMessageHandlerForName:@"sayhello"];

    }

    #pragma mark - WKScriptMessageHandler

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{

        NSLog(@"name:%@\\n body:%@\\n frameInfo:%@\\n",message.name,message.body,message.frameInfo);

    }

    @end

    上面的OC代码如果认证测试一下就会发现dealloc并不会执行,这样肯定是不行的,会造成内存泄漏。原因是[userContentController addScriptMessageHandler:self name:@"sayhello"];这句代码造成无法释放内存。(ps:试了下用weak指针还是不能释放,不知道是什么原因。)因此还需要进一步改进,正确的写法是用一个新的controller来处理,新的controller再绕用delegate绕回来。

    oc代码(正确写法):

    @interface ViewController (){

        WKWebView * webView;

        WKUserContentController* userContentController;

    }

    @end

    @implementation ViewController

    #pragma mark - lifeCircle

    - (void)viewDidLoad {

        [super viewDidLoad];

        //配置环境

        WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];

        userContentController =[[WKUserContentController alloc]init];

        configuration.userContentController = userContentController;

        webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration];

        //注册方法

        WKDelegateController * delegateController = [[WKDelegateController alloc]init];

        delegateController.delegate = self;

        [userContentController addScriptMessageHandler:delegateController  name:@"sayhello"];

        [self.view addSubview:webView];

        [webView mas_makeConstraints:^(MASConstraintMaker *make) {

            make.left.equalTo(self.view);

            make.right.equalTo(self.view);

            make.top.equalTo(self.view);

            make.bottom.equalTo(self.view);

        }];

        webView.UIDelegate = self;

        webView.navigationDelegate = self;

        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]];

    }

    - (void)dealloc{

        //这里需要注意,前面增加过的方法一定要remove掉。

        [userContentController removeScriptMessageHandlerForName:@"sayhello"];

    }

    #pragma mark - WKScriptMessageHandler

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{

        NSLog(@"name:%@\\n body:%@\\n frameInfo:%@\\n",message.name,message.body,message.frameInfo);

    }

    @end

    WKDelegateController代码:

    #import

    #import

    @protocol WKDelegate

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

    @end

    @interface WKDelegateController : UIViewController

    @property (weak , nonatomic) id delegate;

    @end

    .m代码:

    #import "WKDelegateController.h"

    @interface WKDelegateController ()

    @end

    @implementation WKDelegateController

    - (void)viewDidLoad {

        [super viewDidLoad];

    }

    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{

        if ([self.delegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)]) {

            [self.delegate userContentController:userContentController didReceiveScriptMessage:message];

        }

    }

    @end

    h5代码:

    <html>

    <head>

        <script>

    function say()

    {

    //前端需要用 window.webkit.messageHandlers.注册的方法名.postMessage({body:传输的数据} 来给native发送消息

        window.webkit.messageHandlers.sayhello.postMessage({body: 'hello world!'});

    }

    </script>

    </head>

        <body>

            <h1>hello world</h1>

            <button onclick="say()">say hello</button>

        </body>

    </html>

    打印出的log:

    name:sayhello

    body:{

        body = "hello world!";

    }

    frameInfo: { URL: http://www.test.com/ }>

    注意点

    • addScriptMessageHandler要和removeScriptMessageHandlerForName配套出现,否则会造成内存泄漏。

    • h5只能传一个参数,如果需要多个参数就需要用字典或者json组装。


    oc调用JS方法

    代码如下:

    - (void)webView:(WKWebView *)tmpWebView didFinishNavigation:(WKNavigation *)navigation{

        //say()是JS方法名,completionHandler是异步回调block

        [webView evaluateJavaScript:@"say()" completionHandler:^(id _Nullable result, NSError * _Nullable error) {

            NSLog(@"%@",result);

        }];

    }

    h5代码同上。

    WebViewJavascriptBridge


    一般来说,一个好的UI总有一个大神会开发出一个好的第三方封装框架。WebViewJavascriptBridge的作者也做了一套支持WKWebView与JS交互的第三方框架:WKWebViewJavascriptBridge。

    • cocoaPods: pod ‘WebViewJavascriptBridge’, ‘~> 5.0.5’

    • github地址:https://github.com/marcuswestin/WebViewJavascriptBridge

    主要方法如下:

    //初始化方法

    + (instancetype)bridgeForWebView:(WKWebView*)webView;

    + (void)enableLogging;

    //注册函数名

    - (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;

    //调用函数名

    - (void)callHandler:(NSString*)handlerName;

    - (void)callHandler:(NSString*)handlerName data:(id)data;

    - (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;

    //重置

    - (void)reset;

    //设置WKNavigationDelegate

    - (void)setWebViewDelegate:(id)webViewDelegate;

    基本的实现方法和上面写的差不多,就是封装了一下,有兴趣的童鞋可以自己pod下来使用。

  • 相关阅读:
    C/C++ 编写一个通用的Makefile 来编译.c .cpp 或混编
    C/C++ 定义接口文件格式
    MySql存储过程例子1
    项目所遇问题
    linux下编译C++程序无法链接Mysql的问题
    linux 同步时间 调试core内核
    CentOS安装与更新git
    03 js基本数据类型、 js运算符1
    02 js运行原理 、js开发工具介绍 、js程序入门、 js基本语法
    01 js基本介绍
  • 原文地址:https://www.cnblogs.com/fengmin/p/5979417.html
Copyright © 2011-2022 走看看