How to determine the content size of a WKWebView?
我正在尝试在iOS 8及更高版本下运行时,用WKWebView实例替换动态分配的UIWebView实例,但找不到确定WKWebView内容大小的方法。
我的Web视图嵌入在较大的UIScrollView容器中,因此,我需要确定Web视图的理想大小。 这将允许我修改其框架以显示其所有HTML内容,而无需在Web视图内滚动,而且我将能够为滚动视图容器设置正确的高度(通过设置scrollview.contentSize)。
我尝试过sizeToFit和sizeThatFits没有成功。 这是我的代码,该代码创建WKWebView实例并将其添加到容器scrollview中:
1 2 3 4 5 6 7 8 9 10 11 | // self.view is a UIScrollView sized to something like 320.0 x 400.0. CGRect wvFrame = CGRectMake(0, 0, self.view.frame.size.width, 100.0); self.mWebView = [[[WKWebView alloc] initWithFrame:wvFrame] autorelease]; self.mWebView.navigationDelegate = self; self.mWebView.scrollView.bounces = NO; self.mWebView.scrollView.scrollEnabled = NO; NSString *s = ... // Load s from a Core Data field. [self.mWebView loadHTMLString:s baseURL:nil]; [self.view addSubview:self.mWebView]; |
这是一个实验性的didFinishNavigation方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | - (void)webView:(WKWebView *)aWebView didFinishNavigation:(WKNavigation *)aNavigation { CGRect wvFrame = aWebView.frame; NSLog(@"original wvFrame: %@\ ", NSStringFromCGRect(wvFrame)); [aWebView sizeToFit]; NSLog(@"wvFrame after sizeToFit: %@\ ", NSStringFromCGRect(wvFrame)); wvFrame.size.height = 1.0; aWebView.frame = wvFrame; CGSize sz = [aWebView sizeThatFits:CGSizeZero]; NSLog(@"sizeThatFits A: %@\ ", NSStringFromCGSize(sz)); sz = CGSizeMake(wvFrame.size.width, 0.0); sz = [aWebView sizeThatFits:sz]; NSLog(@"sizeThatFits B: %@\ ", NSStringFromCGSize(sz)); } |
这是生成的输出:
1 2 3 4 | 2014-12-16 17:29:38.055 App[...] original wvFrame: {{0, 0}, {320, 100}} 2014-12-16 17:29:38.055 App[...] wvFrame after sizeToFit: {{0, 0}, {320, 100}} 2014-12-16 17:29:38.056 App[...] wvFrame after sizeThatFits A: {320, 1} 2014-12-16 17:29:38.056 App[...] wvFrame after sizeThatFits B: {320, 1} |
sizeToFit调用无效,并且sizeThatFits始终返回高度1。
我想我已经阅读了有关该主题的所有答案,而我所拥有的只是解决方案的一部分。我大部分时间都在尝试实现@davew所描述的KVO方法,这种方法偶尔会起作用,但是大部分时间在WKWebView容器的内容下方留有空白。我还实现了@David Beck建议,并将容器高度设置为0,从而避免了如果容器高度大于内容容器高度时出现问题的可能性。尽管如此,我偶尔还是有空白。
因此,对我来说," contentSize"观察者有很多缺陷。我没有很多Web技术方面的经验,因此我无法回答此解决方案的问题,但是我看到,如果我仅在控制台中打印高度,但不对其进行任何操作(例如,调整约束大小),它跳到某个数字(例如5000),然后跳到最高的数字之前的数字(例如2500-原来是正确的数字)。如果我确实将高度约束设置为从" contentSize"获得的高度,则它将自身设置为它获得的最高数字,并且永远不会将其大小调整为正确的数字-再次由@David Beck评论提到。
经过大量的实验,我设法找到了适合我的解决方案:
1 2 3 4 5 6 7 8 9 10 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in if complete != nil { self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in self.containerHeight.constant = height as! CGFloat }) } }) } |
当然,正确设置约束很重要,以便scrollView根据containerHeight约束调整大小。
事实证明,didFinish导航方法从未在我需要的时候被调用,但是设置了
您可以使用键值观察(KVO)...
在您的ViewController中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | - (void)viewDidLoad { ... [self.webView.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; } - (void)dealloc { [self.webView.scrollView removeObserver:self forKeyPath:@"contentSize" context:nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == self.webView.scrollView && [keyPath isEqual:@"contentSize"]) { // we are here because the contentSize of the WebView's scrollview changed. UIScrollView *scrollView = self.webView.scrollView; NSLog(@"New contentSize: %f x %f", scrollView.contentSize.width, scrollView.contentSize.height); } } |
这样可以节省JavaScript的使用,并让您随时了解所有更改。
我最近不得不亲自处理这个问题。最后,我使用了Chris McClenaghan提出的解决方案的修改。
实际上,他的原始解决方案非常好,并且可以在大多数简单情况下使用。但是,它仅对带有文字的页面有效。它也可能适用于具有静态高度图像的页面。但是,当图像的大小由
这是因为加载页面后可以调整这些元素的大小。因此,实际上,在
监控
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 41 42 43 44 | var shouldListenToResizeNotification = false lazy var webView:WKWebView = { //Javascript string let source ="window.onload=function () {window.webkit.messageHandlers.sizeNotification.postMessage({justLoaded:true,height: document.body.scrollHeight});};" let source2 ="document.body.addEventListener( 'resize', incrementCounter); function incrementCounter() {window.webkit.messageHandlers.sizeNotification.postMessage({height: document.body.scrollHeight});};" //UserScript object let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true) let script2 = WKUserScript(source: source2, injectionTime: .atDocumentEnd, forMainFrameOnly: true) //Content Controller object let controller = WKUserContentController() //Add script to controller controller.addUserScript(script) controller.addUserScript(script2) //Add message handler reference controller.add(self, name:"sizeNotification") //Create configuration let configuration = WKWebViewConfiguration() configuration.userContentController = controller return WKWebView(frame: CGRect.zero, configuration: configuration) }() func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let responseDict = message.body as? [String:Any], let height = responseDict["height"] as? Float else {return} if self.webViewHeightConstraint.constant != CGFloat(height) { if let _ = responseDict["justLoaded"] { print("just loaded") shouldListenToResizeNotification = true self.webViewHeightConstraint.constant = CGFloat(height) } else if shouldListenToResizeNotification { print("height is \\(height)") self.webViewHeightConstraint.constant = CGFloat(height) } } } |
到目前为止,这种解决方案是我能想到的最优雅的解决方案。但是,您应该注意两件事。
首先,在加载URL之前,应将
但是,最重要的是,您需要注意以下几点:
如果采用此解决方案,则需要考虑到,如果将
请注意这一点,因为很容易进入无限循环。例如,如果您决定通过使您的身高等于报告的身高+一些额外的填充物来处理通知:
1 2 3 4 5 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let responseDict = message.body as? [String:Float], let height = responseDict["height"] else {return} self.webViewHeightConstraint.constant = CGFloat(height+8) } |
如您所见,由于我在报告的高度上增加了8,因此完成后,我的
警惕这种情况,否则就可以了。
并且,如果您发现此解决方案有任何问题,请告诉我-我自己依靠它,所以最好知道是否有一些我没有发现的故障!
为我工作
1 2 3 4 5 6 7 | extension TransactionDetailViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.webviewHeightConstraint.constant = webView.scrollView.contentSize.height } } } |
请尝试以下方法。无论您在哪里实例化WKWebView实例,都应添加类似于以下内容的内容:
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 | //Javascript string NSString * source = @"window.webkit.messageHandlers.sizeNotification.postMessage({width: document.width, height: document.height});"; //UserScript object WKUserScript * script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; //Content Controller object WKUserContentController * controller = [[WKUserContentController alloc] init]; //Add script to controller [controller addUserScript:script]; //Add message handler reference [controller addScriptMessageHandler:self name:@"sizeNotification"]; //Create configuration WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc] init]; //Add controller to configuration configuration.userContentController = controller; //Use whatever you require for WKWebView frame CGRect frame = CGRectMake(...?); //Create your WKWebView instance with the configuration WKWebView * webView = [[WKWebView alloc] initWithFrame:frame configuration:configuration]; //Assign delegate if necessary webView.navigationDelegate = self; //Load html [webView loadHTMLString:@"some html ..." baseURL:[[NSBundle mainBundle] bundleURL]]; |
然后添加一个类似于以下方法的方法,该类必须遵守WKScriptMessageHandler协议来处理该消息:
1 2 3 4 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { CGRect frame = message.webView.frame; frame.size.height = [[message.body valueForKey:@"height"] floatValue]; message.webView.frame = frame;} |
这对我有用。
如果您的文档中包含多个文本,则可能需要像这样包装javascript以确保所有内容均已加载:
1 | @"window.onload=function () { window.webkit.messageHandlers.sizeNotification.postMessage({width: document.width, height: document.height});};" |
注意:此解决方案不能解决文档的持续更新。
您还可以通过EvaluationJavaScript获得WKWebView的内容高度。
1 2 3 4 5 6 7 8 9 10 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { [webView evaluateJavaScript:@"Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight)" completionHandler:^(id _Nullable result, NSError * _Nullable error) { if (!error) { CGFloat height = [result floatValue]; // do with the height } }]; } |
您需要等待网络视图完成加载。这是我使用的工作示例
WKWebView内容加载函数永远不会被调用
然后,在webview完成加载后,您可以通过以下方式确定所需的高度
1 2 3 4 5 | func webView(webView: WKWebView!, didFinishNavigation navigation: WKNavigation!) { println(webView.scrollView.contentSize.height) } |
多数答案使用" document.body.offsetHeight"。
这隐藏了身体的最后一个对象。
我通过使用KVO观察器侦听WKWebview" contentSize"中的更改,然后运行以下代码来克服了此问题:
1 2 3 4 5 6 7 8 9 10 | self.webView.evaluateJavaScript( "(function() {var i = 1, result = 0; while(true){result = document.body.children[document.body.children.length - i].offsetTop + document.body.children[document.body.children.length - i].offsetHeight; if (result > 0) return result; i++}})()", completionHandler: { (height, error) in let height = height as! CGFloat self.webViewHeightConstraint.constant = height } ) |
这不是最漂亮的代码,但是对我有用。
我发现hlung在这里对WKWebView进行如下扩展是我最简单,最有效的解决方案:
https://gist.github.com/pkuecuekyan/f70096218a6b969e0249427a7d324f91
他的评论如下:
"很好!对我而言,我没有设置webView.frame,而是设置了自动布局内在ContentSize。"
他的代码如下:
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 | import UIKit import WebKit class ArticleWebView: WKWebView { init(frame: CGRect) { let configuration = WKWebViewConfiguration() super.init(frame: frame, configuration: configuration) self.navigationDelegate = self } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override var intrinsicContentSize: CGSize { return self.scrollView.contentSize } } extension ArticleWebView: WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.evaluateJavaScript("document.readyState", completionHandler: { (_, _) in webView.invalidateIntrinsicContentSize() }) } } |
使用@Andriy的答案,这个答案我能够设置WKWebView中的contentSize的高度并更改其高度。
这是完整的Swift 4代码:
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | var neededConstraints: [NSLayoutConstraint] = [] @IBOutlet weak var webViewContainer: UIView! @IBOutlet weak var webViewHeight: NSLayoutConstraint! { didSet { if oldValue != nil, oldValue.constant != webViewHeight.constant { view.layoutIfNeeded() } } } lazy var webView: WKWebView = { var source =""" var observeDOM = (function(){ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver, eventListenerSupported = window.addEventListener; return function(obj, callback){ if( MutationObserver ){ // define a new observer var obs = new MutationObserver(function(mutations, observer){ if( mutations[0].addedNodes.length || mutations[0].removedNodes.length ) callback(); }); // have the observer observe foo for changes in children obs.observe( obj, { childList:true, subtree:true }); } else if( eventListenerSupported ){ obj.addEventListener('DOMNodeInserted', callback, false); obj.addEventListener('DOMNodeRemoved', callback, false); } }; })(); // Observe a specific DOM element: observeDOM( document.body ,function(){ window.webkit.messageHandlers.sizeNotification.postMessage({'scrollHeight': document.body.scrollHeight,'offsetHeight':document.body.offsetHeight,'clientHeight':document.body.clientHeight}); }); """ let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true) let controller = WKUserContentController() controller.addUserScript(script) controller.add(self, name:"sizeNotification") let configuration = WKWebViewConfiguration() configuration.userContentController = controller let this = WKWebView(frame: .zero, configuration: configuration) webViewContainer.addSubview(this) this.translatesAutoresizingMaskIntoConstraints = false this.scrollView.isScrollEnabled = false // constraint for webview when added to it's superview neededConstraints += NSLayoutConstraint.constraints(withVisualFormat:"H:|[web]|", options: [], metrics: nil, views: ["web": this]) neededConstraints += NSLayoutConstraint.constraints(withVisualFormat:"V:|[web]|", options: [], metrics: nil, views: ["web": this]) return this }() override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) _ = webView // to create constraints needed for webView NSLayoutConstraint.activate(neededConstraints) let url = URL(string:"https://www.awwwards.com/")! let request = URLRequest(url: url) webView.load(request) } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if let body = message.body as? Dictionary<String, CGFloat>, let scrollHeight = body["scrollHeight"], let offsetHeight = body["offsetHeight"], let clientHeight = body["clientHeight"] { webViewHeight.constant = scrollHeight print(scrollHeight, offsetHeight, clientHeight) } } |
这是@IvanMih答案的略微修改。对于那些在
1 2 3 4 5 6 7 8 9 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in if complete != nil { let height = webView.scrollView.contentSize print("height of webView is: \\(height)") } }) } |
因此,基本上不是使用
我尝试了滚动视图KVO,并尝试使用
最终对我有用的是:
我使用KVO收听
1 | [webview addObserver: self forKeyPath: NSStringFromSelector(@selector(loading)) options: NSKeyValueObservingOptionNew context: nil]; |
然后:
1 2 3 4 5 6 7 8 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if(object == self.webview && [keyPath isEqualToString: NSStringFromSelector(@selector(loading))]) { NSNumber *newValue = change[NSKeyValueChangeNewKey]; if(![newValue boolValue]) { [self updateWebviewFrame]; } } } |
1 2 3 4 5 | [self.webview evaluateJavaScript: @"document.body.scrollHeight" completionHandler: ^(id response, NSError *error) { CGRect frame = self.webview.frame; frame.size.height = [response floatValue]; self.webview.frame = frame; }]; |
经过大量的实验,我设法找到了一个对我有用的解决方案,我发现无需使用javascript即可使webview高动态化,也无需从webview中获取高度常数,这就像魅力一样与我一起工作,并且在我注入新的东西时也可以工作样式转换为HTML并播放字体大小和高度
Swift中的代码
1-给您的Webview导航代表
1 | webView.navigationDelegate = self |
2-委托扩展
1 2 3 4 5 6 7 8 9 10 11 | extension yourclass : WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { // Handel Dynamic Height For Webview Loads with HTML // Most important to reset webview height to any desired height i prefer 1 or 0 webView.frame.size.height = 1 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // here get height constant and assign new height in it if let constraint = (webView.constraints.filter{$0.firstAttribute == .height}.first) { constraint.constant = webView.scrollView.contentSize.height } } |
希望它也与你们一起工作
**请注意,这并不是我在StackOverflow和其他站点上进行大量搜索所花费的全部精力,这也是我最终通过大量测试工作的结果
还尝试实现不同的方法,最后得出了解决方案。结果,我制作了一个自调整大小的WKWebView,使它的internalContentSize适应其内容的大小。因此,您可以在自动版面配置中使用它。作为示例,我做了一个视图,它可能有助于您在iOS应用上显示数学公式:https://github.com/Mazorati/SVLatexView
我在UITableViewCell中尝试了Javascript版本,并且效果很好。但是,如果要将其放在scrollView中。我不知道为什么,高度可以更高,但不能更短。但是,我在这里找到了UIWebView解决方案。 https://stackoverflow.com/a/48887971/5514452
它也可以在WKWebView中使用。我认为问题是因为WebView需要重新布局,但不知何故它不会缩小而只能放大。我们需要重置高度,它肯定会调整大小。
编辑:设置约束后,我重置了框架高度,因为有时由于将框架高度设置为0而无法使用。
1 2 3 4 5 6 7 8 9 10 11 12 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.webView.frame.size.height = 0 self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in if complete != nil { self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in let webViewHeight = height as! CGFloat self.webViewHeightConstraint.constant = webViewHeight self.webView.frame.size.height = webViewHeight }) } }) } |
对于Webkit中的任何内容,以下代码对我来说都是完美的。确保将以下委托添加到您的类:WKNavigationDelegate。
1 2 3 4 5 6 7 8 9 10 11 12 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.bodyWebView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in if complete != nil { self.bodyWebView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in let heightWebView = height as! CGFloat //heightWebView is the height of the web view }) } }) } } |
分发很重要,因为这样可以确保在加载Web视图结束时获得的高度正确,这是由于html可能具有的元素类型而发生的。