首页

时间和精神的房子
壹只iOS程序员的修行世界,欢迎来访

如果文章对您有所帮助
将是我最大的荣幸

如何准确判断 WebView 加载完成

正常情况下我们把处理网页加载完毕的代码放在 - (void)webViewDidFinishLoad:(UIWebView *)webView里。但 WebViewDidFinishLoad 时网页真的加载完了吗?

官方文档并没有说明 WebViewDidFinishLoad 到底在什么时候被调用,但事实证明在某些情况下 WebViewDidFinishLoad 可能不是你想要的时机。

网页重定向

当网页重定向发生时,网址被重定向几次,WebViewDidFinishLoad 就会被调用几次。所以如果你只想在最后加载完成时调用某些代码,可以通过webView.isLoading来判断。当 WebViewDidFinishLoad 时如果 webView.isLoading == YES 那么说明网页可能发生了重定向。

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if (!webView.isLoading) {
        [self webViewDidFinishLoadCompletely];
    }
}

加载内嵌资源

除了原生方法外,网页的 readyState 属性也可以返回当前加载状态,共有5种。

  1. uninitialized : 还没开始加载
  2. loading : 加载中
  3. loaded : 加载完成
  4. interactive : 结束渲染,用户已经可以与网页进行交互。但内嵌资源还在加载中
  5. complete : 完全加载完成

WebViewDidFinishLoad 被调用时,readyState 可能处在 interactive 和 complete 两种状态。当我们需要对网页中的元素进行修改时,最好在 complete 状态进行,不然我们的修改可能被重置。例如百度登录页(http://wappass.baidu.com/passport)在iPad上打开时,WebViewDidFinishLoad 的 readyState 就是 interactive,这时假设想要在输入框里自动填写账号密码并修改输入框背景为黄色,我们的修改将会在 complete 状态时被重置。

为了解决这个问题,我们可以在 WebViewDidFinishLoad 判断 readyState 的状态,如果不是 complete,则重写 window.onload 或者 document.onreadystatechange 两个方法,他们都可以准确判断内嵌资源加载完毕的时机。然后通过 JSExport 回调 Objective-C 代码(如果是 WKWebView 则通过 MessageHandler 回调)。对 JSExport 还不熟悉可以参考 这篇博客 来简单了解 JSExport 的使用。

- (void)webViewDidStartLoad:(UIWebView *)webView {
    _jsContext = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    _jsContext[@"xfNewsContext"] = _jsExport;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if (!webView.isLoading) {
        NSString *readyState = [webView stringByEvaluatingJavaScriptFromString:@"document.readyState"];
        BOOL complete = [readyState isEqualToString:@"complete"];
        if (complete) {
            [self webViewDidFinishLoadCompletely];
        } else {
            NSString *jsString =
            @"window.onload = function() {"
            @"    xfNewsContext.onload();"
            @"};"
            @"document.onreadystatechange = function () {"
            @"    if (document.readyState == \"complete\") {"
            @"        xfNewsContext.documentReadyStateComplete();"
            @"    }"
            @"};";
            [_webView stringByEvaluatingJavaScriptFromString:jsString];
        }
        NSLog(@"%@", NSStringFromSelector(_cmd));
    }
}

- (void)onload {
    [self webViewDidFinishLoadCompletely];
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)documentReadyStateComplete {
    [self webViewDidFinishLoadCompletely];
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)webViewDidFinishLoadCompletely {
    [self displayContent];
}

在普通网页加载时打印结果是:

  1. documentReadyStateComplete
  2. onload
  3. webViewDidFinishLoad:

而本例中打印结果则是:

  1. webViewDidFinishLoad:
  2. documentReadyStateComplete
  3. onload

不管 webViewDidFinishLoad 在何时调用,webViewDidFinishLoadCompletely 都保证在加载完成的时候触发。

本文的完整代码可以在 XFNewsContentDemo 中查看。本问与上篇博客共用 Demo,所以 Demo 中有大量与本篇不相关的代码。你只需要找到上述引用代码的部分查看即可。

参考资料

[MDN] document.readyState
[w3schools] HTML DOM readyState Property
[CNBlogs]iOS判断UIWebView加载完成的方法

关注作者

分享本文

目录