WKWebview的使用及遇到的问题

今天试了试WKWebView,试出来很多问题.记录如下

初始化方法如下:

- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

4、WKWebView的一些简单属性的介绍

backForwardList            浏览历史
title                      网页标题             支持KVO
URL                        正在显示的URL        支持KVO
loading                    是否正在加载          支持KVO
estimatedProgress          加载进度             支持KVO
canGoBack  canGoForward    能否后退 前进         支持KVO
reload                     重新加载
stopLoading                停止加载
allowsBackForwardNavigationGestures  是否允许侧滑返回上一页

5、WKWebView的加载方法跟UIWebView基本一样

- loadRequest:
- loadHTMLString:
- loadData:

WKWebViewUIDelegate 代理方法

//当需要打开一个新窗口的时候的调用,如a标签的target='_blank',需要返回一个新的Webview
- (nullable WKWebView )webView:(WKWebView )webView createWebViewWithConfiguration:(WKWebViewConfiguration )configuration forNavigationAction:(WKNavigationAction )navigationAction windowFeatures:(WKWindowFeatures )windowFeatures

以下三个类似alert的代理,一定要调用completionHandler(),这个回调,告诉webview结果

//webview上需要弹出alert的时候调用此方法,如果不实现此方法,则webview的alert是显示不出来,alert类似于只有确定按钮的UIAlertView
- (void)webView:(WKWebView )webView runJavaScriptAlertPanelWithMessage:(NSString )message initiatedByFrame:(WKFrameInfo )frame completionHandler:(void (^)(void))completionHandler

//webview上需要弹出confirm的时候调用此方法,如果不实现此方法,则webview的confirm是显示不出来,confirm类似于有确定按钮和取消按钮的UIAlertView
- (void)webView:(WKWebView )webView runJavaScriptConfirmPanelWithMessage:(NSString )message initiatedByFrame:(WKFrameInfo )frame completionHandler:(void (^)(BOOL result))completionHandler

// webview上需要弹出prompt的时候调用此方法,如果不实现此方法,则webview的prompt是显示不出来,prompt类似于带一个textField的UIAlertView。defaultText相当于textField的placeholed
- (void)webView:(WKWebView )webView runJavaScriptTextInputPanelWithPrompt:(NSString )prompt defaultText:(nullable NSString )defaultText initiatedByFrame:(WKFrameInfo )frame completionHandler:(void (^)(NSString  __nullable result))completionHandler

//window.close() 的时候调用
- (void)webViewDidClose:(WKWebView *)webView

WKWebViewNavigationDelegate代理方法

//决定是否允许发起这个请求
- (void)webView:(WKWebView )webView decidePolicyForNavigationAction:(WKNavigationAction )navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler

//在webview有响应之后,再次决定是否允许这个请求
- (void)webView:(WKWebView )webView decidePolicyForNavigationResponse:(WKNavigationResponse )navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandle

//webview开始加载的时候调用
- (void)webView:(WKWebView )webView didStartProvisionalNavigation:(null_unspecified WKNavigation )navigation

//webview内容已经加载结束,但是上面的某些资源比如图片加载之前调用
- (void)webView:(WKWebView )webView didCommitNavigation:(null_unspecified WKNavigation )navigation

//webview加载结束的时候调用
- (void)webView:(WKWebView )webView didCommitNavigation:(null_unspecified WKNavigation )navigation

//webview加载失败的时候调用
- (void)webView:(WKWebView )webView didFailProvisionalNavigation:(null_unspecified WKNavigation )navigation withError:(NSError )error

//webview在commit过程中失败的时候调用,例如在didCommitNavigation这个代理方法中调用webview的stopLoading方法
- (void)webView:(WKWebView )webView didFailNavigation:(null_unspecified WKNavigation )navigation withError:(NSError )error

WKScriptMessageHandler

//收到JavaScript回调的时候调用
- (void)userContentController:(WKUserContentController )userContentController didReceiveScriptMessage:(WKScriptMessage )message

WKWebViewConfiguration里面有个userContentController。可通过它为webview注入javaScript代码。并且可以添加监听javaScript的回调。

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

WKUserScript *userScript = [[WKUserScript alloc]initWithSource:@"js代码" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];

[configuration.userContentController addUserScript:userScript];
OC执行JS代码:调用JS代码
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler;
//completionHandler 拥有两个参数,一个是返回错误,一个可以返回执行脚本后的返回值
JS调用App注册过的方法

再WKWebView里面注册供JS调用的方法,是通过WKUserContentController类下面的方法:

- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

scriptMessageHandler是代理回调,JS调用name方法后,OC会调用scriptMessageHandler指定的对象。

JS在调用OC注册方法的时候要用下面的方式:

window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

注意,name(方法名)是放在中间的,messageBody只能是一个对象,如果要传多个值,需要封装成数组,或者字典。整个示例如下:

//OC注册供JS调用的方法

[[_webView configuration].userContentController addScriptMessageHandler:self name:@"closeMe"];

//OC在JS调用方法做的处理

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
  NSLog(@"JS 调用了 %@ 方法,传回参数 %@",message.name,message.body);
}

//JS调用

window.webkit.messageHandlers.closeMe.postMessage(null);

如果你在self的dealloc打个断点,会发现self没有释放!这显然是不行的!谷歌后看到一种解决方法,如下:

@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>

@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;

- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;

@end

@implementation     WeakScriptMessageDelegate

- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate
{
  self = [super init];
  if (self) {
     _scriptDelegate = scriptDelegate;
    }
   return self;
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
   [self.scriptDelegate    userContentController:userContentController didReceiveScriptMessage:message];
}

@end

思路是另外创建一个代理对象,然后通过代理对象回调指定的self,

WKUserContentController *userContentController = [[WKUserContentController alloc] init];    
[userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"closeMe"];

运行代码,self释放了,WeakScriptMessageDelegate却没有释放啊啊啊!
还需在self的dealloc里面 添加这样一句代码:

[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"closeMe"];



坑 —> 页面提示没登陆 或者打开网页白屏 下面做出解答

关于Cookie

同一个应用,不同UIWebView之间的Cookie是自动同步的。它们都是保存在NSHTTPCookieStorage中。 当UIWebView加载一个URL的时候,在加载完成时候,这个URL response中包含的cookie会自动以NSHttpCookie的形式保存到 NSHTTPCookieStorage中。同时,如果在http response中,对cookie进行更新或者删除的话,其结果也会直接反应到NSHTTPCookieStorage 存储的cookie数据中。

//获取保存的cookie
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
//设置
[storage setCookies:cookies forURL:url mainDocumentURL:nil]
//删除
[storage deleteCookie:cookie]

WKWebView cookie同步

WKWebView的cookie并不会再加载url后自动保存到NSHTTPCookieStorage中。

原因是因为现在WKWebView会忽视默认的网络存储, NSURLCache, NSHTTPCookieStorage, NSCredentialStorage。 目前是这样的,WKWebView有自己的进程,同样也有自己的存储空间用来存储cookie和cache, 其他的网络类如NSURLConnection是无法访问到的。 同时WKWebView发起的资源请求也是不经过NSURLProtocol的,导致无法自定义请求。

同一个应用,不同WKWebView之间的cookie同步,可以通过使用同一个WKProcessPool实现cookie的同步。

1.创建ProcessPool的单例,来管理 WKProcessPool.
2. WKWebViewConfiguration *cofi = [[WKWebViewConfiguration alloc] init];
cofi.processPool = [ProcessPool share];

获取Cookie:

NSMutableString *cookieStr = [NSMutableString string];

for (NSHTTPCookie *cookie in NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies) {
    [cookieStr appendString:[NSString stringWithFormat:@"%@=%@",cookie.name,cookie.value]];
}
NSMutableURLRequest *requset = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"www.xxxx.com"]];
[requset addValue:cookieStr forHTTPHeaderField:@"Cookie"];

[self.webV loadRequest:requset];


更多可以去查看stackoverflow上回答:http://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303
以下方法来自网络
+ (NSString *)cookiesForURLString:(NSString *)str{
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:str]]; // 这里的HOST是你web服务器的域名地址
// 比如你之前登录的网站地址是abc.com(当然前面要加http://,如果你服务器需要端口号也可以加上端口号),那么这里的HOST就是http://abc.com

// 设置header,通过遍历cookies来一个一个的设置header
for (NSHTTPCookie *cookie in cookies){

    // cookiesWithResponseHeaderFields方法,需要为URL设置一个cookie为NSDictionary类型的header,注意NSDictionary里面的forKey需要是@"Set-Cookie"
    NSArray *headeringCookie = [NSHTTPCookie cookiesWithResponseHeaderFields:
                                [NSDictionary dictionaryWithObject:
                                 [[NSString alloc] initWithFormat:@"%@=%@",[cookie name],[cookie value]]
                                                            forKey:@"Set-Cookie"]
                                                                      forURL:[NSURL URLWithString:str]];

    // 通过setCookies方法,完成设置,这样只要一访问URL为HOST的网页时,会自动附带上设置好的header
    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:headeringCookie
                                                       forURL:[NSURL URLWithString:str]
                                              mainDocumentURL:nil];
}
return str;
}

共享cookie (评论说 iOS 8 还是不能共享 没实测) 来源于奉强的个人博客 http://fengqiang.leanote.com/post/iOS开发-打通UIWebView和WKWebView的Cookie

1、思路

思路是这样子的,我们可以通过NSHTTPCookieStorage的一个单例,拿到app中所有的UIWebView的cookie,拿到之后再通过让WKWebView执行一段js代码,把这些cookie设置到WKWebView中,这样就可以实现WKWebView获取UIWebView的cookie了。

2、代码

1、新建一个单例

+ (instancetype)sharedWKCookieSyncManager {
    static WKCookieSyncManager *sharedWKCookieSyncManagerInstance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        sharedWKCookieSyncManagerInstance = [[self alloc] init];
    });
    return sharedWKCookieSyncManagerInstance;
}

2、在单例中,新建一个WKProcessPool对象,并且,需要保持这个对象为单例,因为在WKWebView中,只有使用了同一个WKProcessPool的WKWebView,才会共享cookie。

- (WKProcessPool *)processPool {
    if (!_processPool) {
        static dispatch_once_t predicate;
        dispatch_once(&predicate, ^{
            _processPool = [[WKProcessPool alloc] init];
        });
    }

    return _processPool;
}

3、新建一个setCookie方法,在方法中,请求一个不存在的url,并且在回调中,设置cookie

- (void)setCookie {
    //判断系统是否支持wkWebView
    Class wkWebView = NSClassFromString(@"WKWebView");
    if (!wkWebView) {
        return;
    }
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.processPool = self.processPool;
    self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:self.testUrl];
    self.webView.navigationDelegate = self;
    [self.webView loadRequest:request];
}

4、在回调中,设置cookie

#pragma mark - WKNavigationDelegate
- (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;\
    }";

    //拼凑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];
}

四、补充
在后面的开发中,需要设置所有的WKWebView都使用前面那个单例processPool,否则,cookie是不会在WKWebView之间共享的。

再次补充

https://github.com/fq050766/WKWebViewAndUIWebViewCookiesDemo

在这里 作者说 在iOS9和iOS10上面测试,发现uiwebView和wkwebview、AFN的cookies都是互通的了 (没测试)