WKWebView问题小结

内存泄漏

当需要拦截Web页面的Javascript函数时会使用以下方法

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

在Web页面需要调用以下方法

window.webkit.messageHandlers.name.postMessage();

这两个方法的name必须一样才能被WKWebView拦截到。
但是如果不及时移除Handler的话WebView不会被释放。所以只要调用了addScriptMessageHandler方法就要调用相对应的方法移除

- (void)removeScriptMessageHandlerForName:(NSString *)name;

内嵌在Cell里显示不全

在iOS10及以上系统里在TableViewCell中嵌入WKWebView时,滚动TableView时WebView渲染会出错。往往内容显示不出来。

具体解决办法在TableView的代理方法中调用WKWebView的setNeedsLayout方法:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    [webView setNeedsLayout];
}

WKWebView不能打开新窗口<a target=_blank />

如果Web页面的链接是<a target=_blank />时,正常结果是新建窗口打开这个页面。但在WKWebView中,如果没有实现相关的代理方法,那么这个方法失效,点击网页中任何外部链接都没有反应。期望的结果是通过Safari来打开这个外部链接。原来的WebView不进行任何处理。如果这样需要在WebView的代理方法中进行如下处理:

- (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    if (!navigationAction.targetFrame) {    //处理<a target=_blank />这种情况
        NSURL *url = navigationAction.request.URL;
        UIApplication *app = [UIApplication sharedApplication];
        if ([app canOpenURL:url]) {
            [app openURL:url];
        }
    }
}

在WKWebView中动态更改UserAgent

往往在访问我们自己的域名时会修改相关UserAgent上传。而访问其他域名时会将UserAgent修改成默认的。这样就需要选择性的修改UserAgent
首先在程序加载时我们先获取系统默认的UserAgent并将它保存在本地:

+(void)load
{
    UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
    NSString *oldAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
    NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:oldAgent, @"originAgent", nil];
    [[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];    
}

然后在加载新页面时来判断是否为我们自己的域名,调用如下方法针对性的修改UserAgent

- (void)setUserAgent:(NSString *)userAgent
{

    if ([URL.host hasSuffix:baseDomain] {   
      //如果是我们自己的域名将传进来的userAgent设置到http的请求header中
        NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:userAgent, @"UserAgent", nil];
        [[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];
        }
    else{
        //否则设置成系统默认的UserAgent
        NSString *string = [[NSUserDefaults standardUserDefaults] objectForKey:@"originAgent"];
        NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:OutNull(string), @"UserAgent", nil];
        [[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];
    }
}

本地Request的Cookies与WKWebView的Cookies共享

往往我们想将本地URL请求的Cookies同步到WKWebView中,在使用UIWebView时会自动同步我们请求URL的Cookies,但在WKWebView中会失效,因为WKWebView自己独立管理Cookies,不与NSURLRequest的公用。因此要通过一下方法进行设置WKWebview的Cookies。

首先在初始化是执行一下脚本来设置Cookies


-(void)initWebView
{
    WKWebViewConfiguration *webViewconfiguration = [[WKWebViewConfiguration alloc] init];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    if(URL.host hasSuffix:baseDomain){
        //在此处要判断域名是否是自己网站。
        NSString *jScript = [self setCookies];
        WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
                        [wkUController addUserScript:wkUScript];
    }
    webViewconfiguration.userContentController = wkUController;
    WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, width, height) configuration:webViewconfiguration];

}


//执行的脚本,可能略有不同
+(NSString *)setCookies
{
    NSString *script = [NSString string];

    for (NSHTTPCookie *httpCookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies])
    {
        NSString *cookie = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",httpCookie.name,httpCookie.value,httpCookie.domain,httpCookie.path?:@"/"];
        script = [script stringByAppendingString:[NSString stringWithFormat:@"document.cookie='%@';",cookie]];

    }
    return script;
}

然后在创建NSURLRequest对象时手动添加cookies到HTTP的Header中:

- (void)loadRequest:(NSURLRequest *)request
{
    //此处也要判断要加载的url是否是我们自己的域名
    if ([request.URL.host hasSuffix:baseDomain]){
        NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:request.URL];
        NSString *cookies = @""; //设置Cookies
        [mutableRequest addValue:cookies forHTTPHeaderField:@"Cookie"];
    }
    // do request  
}

WKWebView在iOS10及以上系统设置新的Cookies不能覆盖旧的Cookies

在使用WKWebView时发现在iOS10以下的系统中如果按照上面的方法设置完Cookies后会生效,并且会自动覆盖旧的Cookies。而在iOS10及以上系统时设置新的Cookies并不会生效,WKWebView还会使用之前的设置过的Cookies。所以我们在切换用户时需要调用以下方法手动删除Cookies

-(void)clearCookies
{
    WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
            [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
                             completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
                                for (WKWebsiteDataRecord *record  in records){
                                    if ([record.displayName isEqualToString:baseDomain]){   
                                        //判断如果是我们自己的域名那么删除该域名下的Cookies
                                        [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes forDataRecords:@[record] completionHandler:^{

                                            }];
                                    }
                                }
                             }];
}

WKWebView在在iOS10及以上系统清除Cookies失效

使用上面的方法清除Cookies时会发现有时会清除失败。加载新的页面时还会使用旧的Cookies。这是因为清除Cookies的时机不对,我们应该在清除完成的回调内再加载新的页面。

- (void)loadRequest:(NSURLRequest *)request{
    WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
            [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
                             completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
                                BOOL isExit = NO;
                                for (WKWebsiteDataRecord *record  in records){
                                    if ([record.displayName isEqualToString:baseDomain]){   
                                        //判断如果是我们自己的域名那么删除该域名下的Cookies
                                        isExit = YES;
                                        [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes forDataRecords:@[record] completionHandler:^{
                                                [webView loadRequest:request];
                                            }];
                                            break;
                                    }
                                }
                                if (!isExit) {
                                     [webView loadRequest:[mutableRequest copy]];
                                 }
                             }];
}

注意:如果在退出页面时清除Cookies,也会出现清除失败的情况。

Native页面内嵌WKWebView高度计算问题

计算WKWebView的高度是我们往往调用document.body.scrollHeight这个方法来计算。如果在下面的代理方法里调用那么计算的高度是不准确的:

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

}

因为页面内部有很多图片还没有加载出来。所以此时调用document.body.scrollHeight这个方法计算的高度是未加载完的页面高度。
如果在加载结束的代理方法里面调用document.body.scrollHeight,那么页面只能在全部资源下载后才会显示。如果我们想要边加载资源边动态的改变页面高度需要做如下处理:

我们在初始化WebView时监听WebView的加载进度

-(void)initWebView
{
    WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
    [webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
}

然后边加载边计算WebView的高度,这样就不必等页面全部加载完再计算WebView的高度了。

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        self.estimatedProgress = [change[NSKeyValueChangeNewKey] doubleValue];
        if ([change[NSKeyValueChangeOldKey] doubleValue]) {
            [self evaluateJavaScript:@"document.body.scrollHeight" completionHandler:^(id heitht, NSError *error) {
                if (!error) {
                    self.pageHeight = [heitht floatValue];
                }
            }];
        }
    }

}

版权声明:本文为GGGHub原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。