wkwebview 使用

2,232 阅读6分钟

简介

wkwebview是iOS8.0之后才有,它相比uiwebview具有特点:

  • 更多的支持HTML5的特性
  • 官方宣称的高达60fps的滚动刷新率以及内置手势
  • safari相同的JavaScript引擎
  • 将UIWebViewDelegate与UIwebview拆分成14个类以及3个协议
  • 增加加载进度属性

WKNavigationDelegate

#pragma mark 追踪页面加载过程
//页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
    NSLog(@"!!!页面开始加载时");
    NSLog(@"__%s",__func__);
}

//当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
    NSLog(@"!!!当内容开始返回时");
    NSLog(@"__%s",__func__);
}

//页面加载完成后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    NSLog(@"!!!页面加载完成");
    NSLog(@"__%s",__func__);
}

//页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
    NSLog(@"!!!页面加载失败时");
    NSLog(@"__%s",__func__);
}



#pragma mark 页面跳转代理 (收到跳转与决定跳转)
// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
    NSLog(@"!!!接收到服务器跳转请求之后调用");
    NSLog(@"__%s",__func__);
}


//在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

{
    NSLog(@"!!!在收到响应后,决定是否跳转");
    NSLog(@"__%s==%@",__func__,navigationResponse.response.URL.absoluteString);
    //允许跳转
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不允许跳转
    //    decisionHandler(WKNavigationResponsePolicyCancel);
    
}

//在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSLog(@"!!!在发送请求之前,决定是否跳转");
    NSLog(@"__%s==%@",__func__,navigationAction.request.URL.absoluteString);
    //允许跳转
    decisionHandler(WKNavigationActionPolicyAllow);
}


- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
    NSLog(@"__%s",__func__);
}

WKUIDelegate

相比于UIWebview,wkwebview对警告窗、确认面板、输入框并不能直接显示,是通过wkuiDelegate代理方法收到对应的回调方法进行实现

- (void)webViewDidClose:(WKWebView *)webView {
    NSLog(@"%s", __FUNCTION__);
}

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    NSLog(@"%s", __FUNCTION__);
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    
    [self presentViewController:alert animated:YES completion:NULL];
    NSLog(@"%@", message);
}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
    NSLog(@"%s", __FUNCTION__);
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"confirm" message:@"JS调用confirm" preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }]];
    [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }]];
    [self presentViewController:alert animated:YES completion:NULL];
    
    NSLog(@"%@", message);
}

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler {
    NSLog(@"%s", __FUNCTION__);
    
    NSLog(@"%@", prompt);
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"textinput" message:@"JS调用输入框" preferredStyle:UIAlertControllerStyleAlert];
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.textColor = [UIColor redColor];
    }];
    
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler([[alert.textFields lastObject] text]);
    }]];
    
    [self presentViewController:alert animated:YES completion:NULL];
}


//在WKWebView中点击没有反应的时候,可以参考一下处理
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
    NSLog(@"===点击没有反应====");
    return nil;
}

问题

  1. 使用系统的WKUserContentController 交互方法注意VC不销毁,形成了循环引用
  2. 如何在h5 刚开始加载的时候就调用app原生方法?

答1:持有webView的控制器中的在viewDidAppear 中 add,viewDidDisappear中remove

2.wkwebview有个加载时机问题,

   //基本js方法 在代理方法 decidePolicyForNavigationResponse 后弹框
     NSString *baseInfoJs = [NSString stringWithFormat:@"window.iOSInfo=%@",baseInfoStr];//baseInfoStr 传入一些基本信息
    WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:baseInfoJs injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
    [self.webView.configuration.userContentController addUserScript:cookieScript];

WKUserScriptInjectionTimeAtDocumentStart:注入时机为document的元素生成之后,其他内容load之前.
WKUserScriptInjectionTimeAtDocumentEnd:注入时机为document全部load完成,任意子资源load完成之前.

WebViewJavascriptBridge 这个第三方库方便

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="jquery-3.3.1.min.js"></script>
    <script type="text/javascript">

        $(function () {
            appCallJs();
        });

        function appCallJs() {
            $("#blogId").click(function () {
                alert('11');
                shareClick()
            });
        }


        //这是必须要写的,用来初始化一些设置
        function setupWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
            if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
            window.WVJBCallbacks = [callback];
            var WVJBIframe = document.createElement('iframe');
            WVJBIframe.style.display = 'none';
            WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
            document.documentElement.appendChild(WVJBIframe);
            setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
        }


        //这也是固定的, OC 调JS , 需要给OC调用的函数必须写在这个函数里面
        setupWebViewJavascriptBridge(function(bridge) {
            bridge.registerHandler('testJSFunction', function(data, responseCallback) {
                alert('JS方法被调用:'+data);
                responseCallback('js执行过了');
            })
        })


        //这个 shareClick 就是 原生那边 注入的函数 , 通过这个方法来调用原生 和传值
        //parmas 是JS 给OC的数据 , response 是 OC函数被调用之后 再 告诉JS 我被调用了
        //比如微信分享,给Dic给原生 , 原生分享成功后再把结果回调给JS 进行处理
        function shareClick() {
            var params = {'title':'测试分享的标题','content':'测试分享的内容','url':'http://www.baidu.com'};
            WebViewJavascriptBridge.callHandler('shareClick',params,function(response) {
                alert(response);
                console.log(response);
            });
        }
    </script>

</head>
<body>

<button id="blogId"> js调用app方法  </button>

</body>
</html>

app 代码

//注册原生方法给JS调用    js调用app的方法
- (void)registShareFunction
{
    [_bridge registerHandler:@"shareClick" handler:^(id data, WVJBResponseCallback responseCallback) {
        
        NSLog(@"@@@@js调用了app方法");
        
        // data 的类型与 JS中传的参数有关
        NSDictionary *tempDic = data;
        // 在这里执行分享的操作
        NSString *title = [tempDic objectForKey:@"title"];
        NSString *content = [tempDic objectForKey:@"content"];
        NSString *url = [tempDic objectForKey:@"url"];
        
        // 将分享的结果返回到JS中
        NSString *result = [NSString stringWithFormat:@"分享成功:%@,%@,%@",title,content,url];
        responseCallback(result);  //回调给JS
    }];
}

//原生调用JS , JS 中先声明testJSFunction 函数  app调用js的方法
-(void)pp_hander
{
    //testJSFunction 是JS的方法
    [_bridge callHandler:@"testJSFunction" data:@"一个字符串" responseCallback:^(id responseData) {
        
        NSLog(@"调用完JS后的回调:%@",responseData);
        
    }];
}

cookie问题:blog.csdn.net/tencent_bug… 1.wkcookie本页面存取可以正常,跳转到新的wkwebview,取不到对应cookie

业界普遍认为 WKWebView 拥有自己的私有存储,不会将 Cookie 存入到标准的 Cookie 容器 NSHTTPCookieStorage 中。实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,

**WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。 **

解决办法:

1.WKProcessPool
通过让所有 WKWebView 共享同一个 WKProcessPool 实例,可以实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie)数据。不过 WKWebView WKProcessPool 实例在 app 杀进程重启后会被重置,导致 WKProcessPool 中的 Cookie、session Cookie 数据丢失,目前也无法实现 WKProcessPool 实例本地化保存。

2.Workaround
a、WKWebView loadRequest 前,在 request header 中设置 Cookie, 解决首个请求 Cookie 带不上的问题;

注意:document.cookie()无法跨域设置 cookie
b、通过 document.cookie 设置 Cookie 解决后续页面(同域)Ajax、iframe 请求的 Cookie 问题;

WKUserContentController* userContentController = [WKUserContentController new]; 
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie = 'skey=skeyValue';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; [userContentController addUserScript:cookieScript];

这种方案无法解决302请求的 Cookie 问题,比如,第一个请求是 www.a.com,我们通过在 request header 里带上 Cookie 解决该请求的 Cookie 问题,接着页面302跳转到 www.b.com,这个时候 www.b.com 这个请求就可能因为没有携带 cookie 而无法访问。当然,由于每一次页面跳转前都会调用回调函数:

  • (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

可以在该回调函数里拦截302请求,copy request,在 request header 中带上 cookie 并重新 loadRequest。不过这种方法依然解决不了页面 iframe 跨域请求的 Cookie 问题,毕竟-[WKWebView loadRequest:]只适合加载 mainFrame 请求。

iOS11.0推出WKHTTPCookieStore后,cookie的管理将变得非常简单。WKHTTPCookieStore的使用和原理类似于NSHTTPCookieStorage;它将访问时获取的cookies自动存储在本地,下次访问时将自动携带相对应的cookie。