• iOS之WKWebView


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

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

    • 更多的支持HTML5的特性

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

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

    • Safari相同的JavaScript引擎

    • 占用更少的内存

    UIWebView

    2368050-1d0493d228b5bac8.png

     

    WKWebView

    2368050-24e41f21673f40e5.png

    因此,使用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代码:

        function say()
    {
    //前端需要用 window.webkit.messageHandlers.注册的方法名.postMessage({body:传输的数据} 来给native发送消息
        window.webkit.messageHandlers.sayhello.postMessage({body: 'hello world!'});
    }            hello world        say hello

    打印出的log:

    name:sayhello
    body:{
        body = "hello world!";
    }
     frameInfo:<wkframeinfo: 0x7f872060ce20; ismainframe = yes; request =      { 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。

    主要方法如下:

    //初始化方法
    + (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下来使用。


    前不久把项目中的UIWebView更新到WkWebView,解决了一大堆问题,但是也遗留了一大堆问题,比方说cookie。

    以前UIWebView会自动去NSHTTPCookieStorage中读取cookie,但是WKWebView并不会去读取,因此导致cookie丢失以及一系列问题,解决方式就是在request中手动帮其添加上。

    mainWebView.UIDelegate = self;
    mainWebView.navigationDelegate = self;
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]];
    [request addValue:[self readCurrentCookieWithDomain:@"http://www.test.com/"] forHTTPHeaderField:@"Cookie"];
    [mainWebView loadRequest:request];
    
    - (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{
        NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        NSMutableString * cookieString = [[NSMutableString alloc]init];
        for (NSHTTPCookie*cookie in [cookieJar cookies]) {
            [cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value];
        }
    
    //删除最后一个“;”
        [cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)];
        return cookieString;
    }

    但是这只能解决第一次进入的cookie问题,如果页面内跳转(a标签等)还是取不到cookie,因此还要再加代码。

    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    
       //取出cookie
        NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        //js函数
        NSString *JSFuncString =
    @"function setCookie(name,value,expires)
        {
        var oDate=new Date();
        oDate.setDate(oDate.getDate()+expires);
    document.cookie=name+'='+value+';expires='+oDate+';path=/'
        }
        function getCookie(name)
        {
        var arr = document.cookie.match(new RegExp('(^| )'+name+'=({FNXX==XXFN}*)(;|$)'));
        if(arr != null) return unescape(arr[2]); return null;
        }
        function delCookie(name)
        {
        var exp = new Date();
        exp.setTime(exp.getTime() - 1);
        var cval=getCookie(name);
        if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();
        }";
    
        //拼凑js字符串
        NSMutableString *JSCookieString = JSFuncString.mutableCopy;
        for (NSHTTPCookie *cookie in cookieStorage.cookies) {
        NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value];
            [JSCookieString appendString:excuteJSString];
        }
        //执行js
        [webView evaluateJavaScript:JSCookieString completionHandler:nil];
    
    }
  • 相关阅读:
    MySQL数据库常见面试题
    抽象类与接口
    HashMap与Hashtable的区别
    IDEA破解
    重写equals方法
    MFC编程入门之十七(对话框:文件对话框)
    MFC编程入门之十六(对话框:消息对话框)
    MFC编程入门之十五(对话框:一般属性页对话框的创建及显示)
    MFC编程入门之十三(对话框:属性页对话框及相关类的介绍)
    MFC编程入门之十二(对话框:非模态对话框的创建及显示)
  • 原文地址:https://www.cnblogs.com/jx66/p/6375247.html
Copyright © 2020-2023  润新知