WKWebView问题小结

本文介绍了使用 WKWebView 过程中遇到的各种问题及解决方案,包括内存泄漏、内嵌 Cell 显示不全、处理新窗口链接、动态更改 UserAgent、Cookies 管理等方面的技术细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内存泄漏

当需要拦截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];
                }
            }];
        }
    }

}
### 如何在 iOS WKWebView 中设置字体大小 为了调整 `WKWebView` 的字体大小,可以通过自定义 HTML 和 CSS 来实现这一目标。具体来说,在创建 `WKWebView` 实例之前配置其使用的网页内容样式是一个有效的方法。 对于通过字符串形式加载的数据(即使用 `-loadHTMLString:baseURL:` 方法),可以在 HTML 字符串中嵌入 `<style>` 标签来指定全局的字体尺寸: ```objc NSString *htmlContent = @"<html><head>" "<style type='text/css'>" "body { font-size: 20px; } /* 设置整个页面的基础字体大小 */ " "</style></head>" "<body>Hello, world!</body></html>"; [self.webView loadHTMLString:htmlContent baseURL:nil]; ``` 当从 URL 加载资源时,则可以利用 JavaScript 注入的方式动态改变已加载文档中的字体属性。这通常涉及到向 `WKUserScript` 添加一段脚本并将其注入到正在运行的 Web 页面上下文中[^2]。 另外一种方法是借助于 `WKPreferences` 类提供的 API 控制视图内的文本缩放比例,从而间接影响显示出来的文字大小。下面是一段用于增大所有文本元素外观的例子代码片段: ```objc WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; configuration.preferences = [WKWebsiteDataStore nonPersistentDataStore].httpCookieStore; // 增加文本缩放级别至175% configuration.preferences.minimumFontSize = 14; // 这个参数仅适用于非CSS渲染的内容 self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration]; // 使用JavaScript增加字体大小 NSString *jsCode = @"document.getElementsByTagName('body')[0].style.fontSize = '20px';"; WKUserScript *userScript = [[WKUserScript alloc] initWithSource:jsCode injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; [configuration.userContentController addUserScript:userScript]; ``` 值得注意的是,直接操作 DOM 或者覆盖内联样式的做法可能会被某些网站的安全策略阻止;因此建议尽可能地遵循官方指南以及最佳实践来进行开发工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值