近期有一个需求需要在View的某一个区域插入一段H5的内容,这段内容是服务端下发的,相信很多同僚都做过类似的需求,现在就把本人做这个需求的经历记录下来,希望对大家有所帮助。
这个需求的难点是下发的H5内容高度是变化的,根据服务端下发内容的不同,WebView会有不同的高度。所以关键点也是在拿到服务端内容进行WebView加载完成之后,计算出WebView的高度,并且设置为对应的值即可。
只包含文本的H5
在网上找了很多资料,大多数的都是如下的方式,在 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) 代理方法中添加如下代码
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.body.offsetHeight") { result, _ in
if let height = result as? CGFloat {
var frame = webView.frame
frame.size.height = height
webView.frame = frame
}
}
}
上面的方式在大多数的时候是有效正确的,但我遇到了一些情况还是计算的有问题。经过不断的查阅资料,还是找到了一个更准确的计算方式,方式基本和上面是一样的,调用的JS不一样,使用 document.documentElement.offsetHeight,代码如下
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.documentElement.offsetHeight") { result, _ in
if let height = result as? CGFloat {
var frame = webView.frame
frame.size.height = height
webView.frame = frame
}
}
}
至于原因,大致可以如下理解:document.documentElement.offsetHeight >= document.body.offsetHeight,具体详情可以参考 这里,这里描述了 offsetHeight 在各个节点及各个浏览器的一些差异。
还有一点要注意:初始化webView的时候,高度要设置为0,宽度需要设置成需要的宽度
包含文本和图片的H5
正解
上面的方法对于没有图片的H5是完全够用的,但是如果H5中有图片之后,上面的方案就会有一些缺陷,如果图片加载过慢的话,会导致获取的高度偏小,因为有些H5的<img>标签是没有预先设置size的,而且这种情况特别多。我们上面的代码获取高度是在整个DOM加载完后获取高度,此时就有很有可能<img>中的图片没有加载完,此时获取的高度是没有包括<img>中图片的高度的,故而不准确。
此时我们可以在H5中注入一段代码,如下:
<script>
function imagesLoad() {
let images = document.getElementsByTagName('img');
for (var i = 0; i <= images.length - 1; i++) {
(images[i]).onload = function() {
let height = document.documentElement.scrollHeight;
// 给 wkwebkit 一个通知 imageLoaded,并把高度传过去
window.webkit.messageHandlers.imageLoaded.postMessage(height);
}
}
}
</script>
这段代码是用于WKWebView调用的,添加了一个函数imagesLoad(),它给所有的<img>标签添加一个onload函数,此函数是在<img>获取到图片之后调用,这样在所有的图片加载完成后都能获取到最新的高度,然后给 wkwebkit 一个通知 imageLoaded,并把高度传给iOS端。
接下来看一下iOS端如何处理,在 func webViewDidFinishLoad(_ webView: BDWebView) 中添加如下代码
func webViewDidFinishLoad(_ webView: BDWebView) {
// 其他业务逻辑...
webView.evaluateJavaScript("imagesLoad();")
}
而imageLoaded是需要如下处理的
// 要先初始化 configuration 和 userContentController,此处略
webView.configuration.userContentController.add(self, name: "imageLoaded")
还要实现 WKScriptMessageHandler 协议
extension BDWebViewManager: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "imageLoaded" {
if let height = (message.body as? CGFloat) {
// 此处设置webview的高度
}
}
}
}
至此即可获取WKWebView的准确高度。
KVO误解
查资料时有使用KVO的方式获取WebView高度,此方法经本人测试是有问题的,如果webview每次加载的内容都比上一次更多,webview的高度更高的话是可以的,或者加载一次性的H5内容也是可以的,但是如果使用同一个WebView多次加载不同的内容,就会出现一些问题,比如第一次加载H5的内容获取的高度是1000,而第二次加载的内容高度是800,此时下方或多出约200的空白区域,所以如果使用同一个WebView多次加载不同的内容,请慎用此方法。