正常情况下我们把处理网页加载完毕的代码放在 - (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种。
- uninitialized : 还没开始加载
- loading : 加载中
- loaded : 加载完成
- interactive : 结束渲染,用户已经可以与网页进行交互。但内嵌资源还在加载中
- 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];
}
在普通网页加载时打印结果是:
- documentReadyStateComplete
- onload
- webViewDidFinishLoad:
而本例中打印结果则是:
- webViewDidFinishLoad:
- documentReadyStateComplete
- onload
不管 webViewDidFinishLoad 在何时调用,webViewDidFinishLoadCompletely 都保证在加载完成的时候触发。
本文的完整代码可以在 XFNewsContentDemo 中查看。本问与上篇博客共用 Demo,所以 Demo 中有大量与本篇不相关的代码。你只需要找到上述引用代码的部分查看即可。
参考资料
[MDN] document.readyState
[w3schools] HTML DOM readyState Property
[CNBlogs]iOS判断UIWebView加载完成的方法