如何準確判斷 WebView 載入完成
正常情況下我們把處理網頁載入完畢的程式碼放在 - (void)webViewDidFinishLoad:(UIWebView *)webView裡。但 WebViewDidFinishLoad 時網頁真的載入完了嗎?
官方文件並沒有說明 WebViewDidFinishLoad 到底在什麼時候被呼叫,但事實證明在某些情況下 WebViewDidFinishLoad 可能不是你想要的時機。
網頁重定向
當網頁重定向發生時,網址被重定向幾次,WebViewDidFinishLoad 就會被呼叫幾次。所以如果你只想在最後載入完成時呼叫某些程式碼,可以通過webView.isLoading來判斷。當 WebViewDidFinishLoad 時如果 webView.isLoading == YES 那麼說明網頁可能發生了重定向。
1 2 3 4 5 |
- (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 還不熟悉可以參考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
- (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 都保證在載入完成的時候觸發。
http://www.cocoachina.com/ios/20170314/18881.html
https://github.com/xiaofei86/XFBlogDemo
在網頁本身存在約束情況下出現一直變大的BUG,iOS8.0後改用WKWebView
// 頁面載入完呼叫
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
{
WeakSelf
//獲取內容實際高度(畫素)@"document.getElementById(\"content\").offsetHeight;"
[webView evaluateJavaScript:@"document.body.scrollHeight" completionHandler:^(id _Nullable result,NSError * _Nullable error) {
// 此處js字串採用scrollHeight而不是offsetHeight是因為後者並獲取不到高度,看參考資料說是對於載入html字串的情況下使用後者可以(@"document.getElementById(\"content\").offsetHeight;"),但如果是和我一樣直接載入原站內容使用前者更合適
//獲取頁面高度,並重置webview的frame
NSLog(@"result此處就是實際高度%@",result);
}];
}