在 iOS 开发中,我们经常会用 WKWebView
来加载和展示 H5 页面。通常,开发者会在 WKWebView
的 didFinish
方法中处理页面加载完成后的逻辑,例如更新 UI 或执行后续操作。然而,didFinish
的触发时机并不总是如我们所期待,它并不会等待所有异步操作(如 AJAX 请求、图片加载等)完成后再执行。
本文将深入探讨 didFinish
的触发时机及其受哪些加载内容的影响,同时提供一种方法,确保页面在所有异步操作完成后再触发回调通知 iOS。
WKWebView 的 didFinish 回调触发时机
在 iOS 的 WKWebView
中,didFinish
回调的触发代表了页面 HTML 文档的初步加载完成。这意味着浏览器已经解析并加载了页面的主要内容结构,但并未考虑页面中的所有资源(如图片、CSS、JavaScript、AJAX 请求等)是否加载完毕。因此,理解 didFinish
的触发时机及其受哪些加载内容影响,对合理设计页面加载完成的处理逻辑至关重要。
以下是 didFinish
回调受影响的几种内容:
-
HTML 文档的解析和加载
didFinish
回调会在 HTML 文档加载完成后触发。也就是说,当WKWebView
获取到并渲染了页面的初始 HTML 结构后,didFinish
就会被触发。这一过程不包括后续的资源加载,因此只代表页面的基础结构已加载完成,而非页面的所有内容。 -
CSS 和 JavaScript 文件的加载
外部 CSS 和 JavaScript 文件的加载也会影响
didFinish
的触发,特别是外部 CSS 的加载。浏览器在初步渲染页面时需要完整的样式信息,因此通常会等待 CSS 文件的加载完成再触发didFinish
。同样地,对于页面中同步加载的 JavaScript(没有
async
或defer
标记的脚本),浏览器会在加载并执行完脚本后才触发didFinish
。然而,异步加载的 JavaScript 则不影响该回调,它们的执行可能在didFinish
触发后继续进行。 -
图片和其他媒体资源的加载
页面中的图片、视频和音频等媒体资源的加载时间不影响
didFinish
的触发。即使图片还未完全加载,didFinish
依然会在 HTML 和 CSS 加载完成后触发。因此,如果页面中有大量的图片或其他媒体文件,didFinish
回调会提前触发,而图片加载可能仍在进行。 -
AJAX 请求和动态内容加载
许多现代 H5 页面会在加载后发起 AJAX 请求以获取动态数据。
didFinish
回调不会等待 AJAX 请求完成,即使这些请求的数据对页面显示至关重要。也就是说,页面在视觉上可能看似完成加载,但实际数据尚未加载完毕。因此,若希望在 AJAX 请求完成后再触发回调,必须通过额外的手段进行控制。 -
<iframe>
和嵌套资源的加载如果页面中包含
<iframe>
元素,didFinish
会在该iframe
的内容加载完成后触发。嵌套的iframe
也是如此,didFinish
只会在最外层框架的内容加载完成时触发。然而,嵌套框架中的进一步异步加载操作,如同 AJAX 请求和媒体加载一样,也不会影响didFinish
的触发。
如何确保页面所有异步操作完成后再触发 iOS 回调
由于 didFinish
回调的触发不依赖于页面的所有资源加载和异步操作完成,若我们希望在页面的所有异步请求和资源加载完毕后再通知 iOS 端,可以通过以下方法实现:
-
在 H5 页面中实现异步操作的完成检测
可以在页面中使用 JavaScript 控制异步操作的完成。例如,使用Promise.all
等手段确保页面中的所有 AJAX 请求完成后触发一个事件,表示页面所有内容已经加载完毕。 -
使用 JavaScript 向 iOS 端发送消息
在 H5 页面所有异步操作完成后,通过window.webkit.messageHandlers
向 iOS 发送消息,以便触发 iOS 的相应回调,从而达到“所有资源加载完成后再通知 iOS”的效果。
代码示例
以下是一个完整的实现示例:
1. H5 页面代码(JavaScript)
在页面中使用 Promise.all
来监听所有异步操作完成后再通知 iOS:
function onAllAsyncOperationsComplete() {
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.notifyCompletion) {
window.webkit.messageHandlers.notifyCompletion.postMessage("all_done");
}
}
// 假设页面有多个 AJAX 请求
Promise.all([
fetch('https://example.com/api/data1'),
fetch('https://example.com/api/data2'),
// 其他异步操作
]).then(responses => {
// 确保所有异步操作完成后调用 onAllAsyncOperationsComplete
onAllAsyncOperationsComplete();
}).catch(error => {
console.error("异步操作失败:", error);
});
2. iOS 端代码(Swift)
在 iOS 端,通过注册 JavaScript 消息处理来接收通知,并在接收到通知后执行后续操作:
import WebKit
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let contentController = WKUserContentController()
contentController.add(self, name: "notifyCompletion") // 注册 JavaScript 通知
let config = WKWebViewConfiguration()
config.userContentController = contentController
webView = WKWebView(frame: self.view.bounds, configuration: config)
webView.navigationDelegate = self
self.view.addSubview(webView)
if let url = URL(string: "https://yourwebsite.com") {
let request = URLRequest(url: url)
webView.load(request)
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// 在页面加载完成后执行其他逻辑
print("页面加载完成")
}
// 处理 JavaScript 中发送的通知
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "notifyCompletion" {
if let body = message.body as? String, body == "all_done" {
print("所有异步操作已完成,触发回调")
// 执行你希望在所有异步操作完成后执行的代码
}
}
}
}
总结
WKWebView
的 didFinish
回调在 HTML 文档结构加载完成后触发,但不会等待页面中的所有异步操作和资源加载完成。因此,为了确保在页面全部资源加载完毕后再通知 iOS,可以通过 H5 页面中的 JavaScript 控制异步加载的完成状态,并使用 window.webkit.messageHandlers
与 iOS 端通信,从而实现更准确的加载完成时机控制。