如何确定一个WKWebView的内容大小?

我正在尝试在iOS 8和更新版本下运行时用WKWebView实例replacedynamic分配的UIWebView实例,而我找不到确定WKWebView内容大小的方法。

我的Web视图embedded在一个更大的UIScrollView容器中,因此我需要确定Web视图的理想大小。 这将允许我修改其框架以显示其所有的HTML内容,而无需在Web视图中滚动,我将能够为滚动视图容器(通过设置scrollview.contentSize)设置正确的高度。

我试过sizeToFit和sizeThatFits没有成功。 这是我的代码,创build一个WKWebView实例,并将其添加到容器滚动视图:

// 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方法:

 - (void)webView:(WKWebView *)aWebView didFinishNavigation:(WKNavigation *)aNavigation { CGRect wvFrame = aWebView.frame; NSLog(@"original wvFrame: %@\n", NSStringFromCGRect(wvFrame)); [aWebView sizeToFit]; NSLog(@"wvFrame after sizeToFit: %@\n", NSStringFromCGRect(wvFrame)); wvFrame.size.height = 1.0; aWebView.frame = wvFrame; CGSize sz = [aWebView sizeThatFits:CGSizeZero]; NSLog(@"sizeThatFits A: %@\n", NSStringFromCGSize(sz)); sz = CGSizeMake(wvFrame.size.width, 0.0); sz = [aWebView sizeThatFits:sz]; NSLog(@"sizeThatFits B: %@\n", NSStringFromCGSize(sz)); } 

这里是生成的输出:

 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的高度。

虽然这个问题已经有相当一段时间的问题了,笔者也许已经提出了,但是我想给出我的答案,因为像我这样的人可能会试图find解决scheme。

我想我读这个问题的每一个答案,我所有的是解决scheme的一部分。 大部分时间我都试图按照@davew的描述来实现KVO方法,偶尔工作,但是大部分时间在WKWebView容器的内容下留下了一个空白区域。 我也实现了@David Beck的build议,使容器高度为0,避免了容器高度大于内容的问题。 尽pipe如此,我有那个偶尔的空白。 所以,对我来说,“contentSize”观察者有很多缺陷。 我没有很多web技术的经验,所以我不能回答这个解决scheme有什么问题,但我看到,如果我只在控制台打印高度,但不做任何事情(例如调整约束),它会跳到某个数字(例如5000),然后跳到最高的那个数字(例如2500 – 这个数字是正确的)。 如果我将高度约束设置为从“contentSize”得到的高度,它将自己设置为最高的数字,并且永远不会调整到正确的大小 – 这又是由@David Beck评论提及的。

经过大量的实验,我find了一个适合我的解决scheme:

 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in if complete != nil { self.webView.evaluateJavaScript("document.body.offsetHeight", completionHandler: { (height, error) in self.containerHeight.constant = height as! CGFloat }) } }) } 

当然,正确设置约束是很重要的,这样scrollView将根据containerHeight约束来resize。

事实certificate,当我想要的时候didFinish导航方法永远不会被调用,但是已经设置了“document.readyState”步骤,下一个(“document.body.offsetHeight”)会在正确的时刻被调用,返回正确的高度。

希望它也适用于你!

你可以使用键值观测(KVO)…

在你的ViewController中:

 - (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: %fx %f", scrollView.contentSize.width, scrollView.contentSize.height); } } 

这将节省JavaScript的使用,并让你在所有的变化循环。

我最近不得不自己处理这个问题。 最后,我使用了Chris McClenaghan提出的解决scheme。

事实上,他的原始解决scheme是相当不错的,它在最简单的情况下工作。 不过,它只适用于带有文字的页面。 它也可能在具有静态高度的图像的页面上工作。 但是,如果您的图像的大小是用max-heightmax-width属性定义的,则它绝对不起作用。

这是因为这些元素可以在页面加载resize。 所以,实际上, onLoad返回的高度总是正确的。 但是这只对那个特定的例子是正确的。 解决方法是监视身高的变化并对其作出响应。

监视document.body大小

 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) } } } 

这个解决scheme是我能想出的最优雅的。 但是,有两件事情你应该知道。

首先,在加载你的URL之前,你应该设置shouldListenToResizeNotificationfalse 。 当加载的URL可以快速更改时,需要额外的逻辑。 发生这种情况时,出于某种原因,来自旧内容的通知可能会与来自新内容的通知重叠。 为了防止这种行为,我创build了这个variables。 它确保了一旦我们开始加载新内容,我们不再处理来自旧内容的通知,并且我们只在加载新内容之后恢复处理resize通知。

但最重要的是,您需要了解这一点:

如果您采用此解决scheme,则需要考虑到如果将WKWebView的大小更改为除通知所报告的大小以外的任何内容,通知将再次触发。

要小心,因为进入无限循环很容易。 例如,如果您决定通过使高度等于报告的高度+一些额外的填充来处理通知:

 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,在完成之后,我的body的大小将会改变,并且通知将被再次发布。

警惕这种情况,否则你应该没问题。

如果您发现此解决scheme有任何问题,请让我知道 – 我自己依靠它,所以最好知道是否有一些我没有发现的错误!

您需要等待webview完成加载。 这是我使用的一个工作示例

WKWebView内容加载function永远不会被调用

然后在webview完成加载后,你可以确定你需要的高度

 func webView(webView: WKWebView!, didFinishNavigation navigation: WKNavigation!) { println(webView.scrollView.contentSize.height) } 

尝试以下。 无论你在哪里实例化你的WKWebView实例,添加类似于以下的东西:

  //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协议来处理消息:

 - (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以确保一切都被加载:

 @"window.onload=function () { window.webkit.messageHandlers.sizeNotification.postMessage({width: document.width, height: document.height});};" 

注:此解决scheme不解决正在进行的文档更新。

使用@ Andriy的答案和这个答案我能够设置获取WKWebView的contentSize的高度,并改变它的高度。

这里是全速4代码:

  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) } } 

我试过滚动视图KVO,我试着在文档上使用clientHeightoffsetHeight等来评估javascript …

最终为我工作的是: document.body.scrollHeight 。 或者使用最顶层元素的scrollHeight ,例如一个容器div

我听着使用KVO loading WKWebview属性更改:

 [webview addObserver: self forKeyPath: NSStringFromSelector(@selector(loading)) options: NSKeyValueObservingOptionNew context: nil]; 

接着:

 - (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]; } } } 

updateWebviewFrame实现:

 [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; }];