如何确定 WKWebView 的内容大小?

Posted

技术标签:

【中文标题】如何确定 WKWebView 的内容大小?【英文标题】:How to determine the content size of a WKWebView? 【发布时间】:2014-12-16 22:34:36 【问题描述】:

我正在尝试在 ios 8 及更高版本下运行时用 WKWebView 实例替换动态分配的 UIWebView 实例,但我找不到确定 WKWebView 内容大小的方法。

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

我试过 sizeToFit 和 sizeThatFits 都没有成功。这是我创建 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。

【问题讨论】:

更新:我仍在寻找解决方案。如果我通过 [self.mWebView loadRequest:req]) 加载远程内容,则可以通过 didFinishNavigation 中的 self.mWebView.scrollView.contentSize 获得大小。但是,如果我通过 [self.mWebView loadHTMLString:s] 加载我的内容,则直到稍后的某个时间才能使用该大小。将 loadRequest 与 dataURL 一起使用并不能解决问题。我不知道“稍后”是什么时候。 我想知道是否会有一个有意义的答案?考虑到网页在任何时候都可能将其内容的大小调整为其窗口的大小,那么我们怎么可能将窗口的大小更改为其内容的大小呢?我认为 WKWebView 不提供intrinsicContentSize 是有原因的,根本没有单一的、有意义的“内在”大小。 2021 年:addUserScript 然后使用 ResizeObserver。 【参考方案1】:

我认为我阅读了有关此主题的所有答案,而我所拥有的只是解决方案的一部分。大部分时间我都花在尝试实现@davew 描述的 KVO 方法,它偶尔会起作用,但大部分时间在 WKWebView 容器的内容下留下了一个空白。我还实现了@David Beck 的建议,并将容器高度设为 0,从而避免了容器高度大于内容高度时出现问题的可能性。尽管如此,我还是有偶尔的空白。 所以,对我来说,“contentSize”观察者有很多缺陷。我没有太多的网络技术经验,所以我无法回答这个解决方案有什么问题,但我看到如果我只在控制台中打印高度而不用它做任何事情(例如,调整约束的大小),它跳到某个数字(例如 5000),然后跳到最高数字之前的数字(例如 2500 - 结果是正确的数字)。如果我确实将高度约束设置为我从“contentSize”获得的高度,它会将自己设置为它获得的最高数字,并且永远不会调整为正确的高度——@David Beck 评论再次提到了这一点。

经过大量实验,我设法找到了适合我的解决方案:

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 导航方法永远不会在我想要的时候被调用,但是设置了document.readyState 步骤后,下一个 (document.body.offsetHeight) 会在正确的时刻被调用,并返回正确的高度数字。

【讨论】:

您可能想改用document.body.scrollHeight,如this gist 和this answer。 在我的情况下不起作用,document.body.offsetHeightdocument.body.scrollHeight 都给出了错误的高度 我已经使它与上面的代码一起工作,但只有在我的 html 字符串中添加了一些元数据之后: 对我来说WKWebView document.body.offsetHeight 返回错误的高度,知道为什么吗? ***.com/questions/54187194/… 对于 iOS 13 document.body.scrollHeight 不工作,所以我使用 document.documentElement.scrollHeight 并且它为我工作【参考方案2】:

您可以使用键值观察 (KVO)...

在您的视图控制器中:

- (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 的使用并让您随时了解所有更改。

【讨论】:

这样做有一个缺点:如果内容更改为小于 Web 视图的高度,则 contentSize 将与 Web 视图的框架相同。 自 ios 10.3 以来,使用这种 ok KVO 会导致一个长的空白页面结束页面的结尾。有人有解决这种情况的方法吗?【参考方案3】:

我最近不得不自己处理这个问题。最后,我使用了solution proposed by Chris McClenaghan的修改。

实际上,他最初的解决方案非常好,并且适用于大多数简单的情况。但是,它只适用于带有文本的页面。它可能也适用于具有静态高度的图像的页面。但是,当您拥有使用max-heightmax-width 属性定义大小的图像时,它肯定不起作用。

这是因为这些元素可以在页面加载后调整大小。所以,实际上,onLoad 返回的高度总是正确的。但它只对那个特定的实例是正确的。解决方法是监听body高度的变化并做出响应。

监控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)
        
        
    

这个解决方案是迄今为止我能想到的最优雅的解决方案。但是,您应该注意两件事。

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

不过,最重要的是,您需要注意这一点:

如果您采用此解决方案,您需要考虑到,如果您将 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 的大小将发生变化,并且通知将再次发布。

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

如果您发现此解决方案有任何问题,请告诉我 - 我自己依赖它,所以最好知道是否有一些我没有发现的错误!

【讨论】:

安德烈,我不能再感谢你了。这将节省我几个小时的谷歌搜索和尝试。只是一些额外的信息:这对我有用的meta viewport标签设置为width=device-width, initial-scale=1.0, shrink-to-fit=no,我删除了shouldListenToResizeNotification = true下的约束设置。 我也能够使用这种方法使其工作。谢谢!。只是好奇,是否有人尝试过此处记录的 webview.scrollView.addObserver 方法? ***.com/a/33289730/30363 这行得通。请记住通过 webView.configuration.userContentController.removeScriptMessageHandler 彻底删除它,否则 Web 视图会保留您的 self 引用,从而导致内存泄漏。 永远不会调用 Source2 事件监听器,我已经嵌入了 instagram 帖子,每次source1 的高度都给我错误的高度 我看到了与 Chlebta 相同的问题 - 这不适用于因社交嵌入而调整大小的页面。【参考方案4】:

为我工作

extension TransactionDetailViewController: WKNavigationDelegate 
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) 
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) 
            self.webviewHeightConstraint.constant = webView.scrollView.contentSize.height
        
    

【讨论】:

此解决方案不保证调用 'didFinish' 后加载将在 0.1 结束 成功了。最佳解决方案。谢谢朋友。 如果我将我的 iPhone 从纵向旋转到横向,那么它会提供太多高度 .async 更改为asyncAfter(deadline: .now() + 0.1) 对我有用。谢谢!【参考方案5】:

试试下面的。无论您在何处实例化 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);;"

注意:此解决方案不解决文档的持续更新问题。

【讨论】:

userContentController:didReceiveScriptMessage: 我收到空字典。 :( 只有将 document.widthdocument.height 更改为 window.innerWidthwindow.innerHeight 才有效,这就是你得到一个空字典的原因,@TimurBernikowich。 对 javascript 了解不多,但 document.body.scrollHeight 帮助我获得了准确的高度。 document.heightwindow.innerHeight 都是 0。window.onload=function () ... 也是必要的。【参考方案6】:

也可以通过evaluateJavaScript获取WKWebView的内容高度。

- (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

                  
              ];

【讨论】:

【参考方案7】:

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

WKWebView Content loaded function never get called

那么在webview加载完成之后,就可以通过

来确定你需要的高度了
func webView(webView: WKWebView!, didFinishNavigation navigation: WKNavigation!) 

   println(webView.scrollView.contentSize.height)


【讨论】:

谢谢!但在我的情况下,至少 didFinishNavigation 为时过早(我得到 0, 0 的大小)。我没有看到在 WKWebView 实例完成加载其内容后调用的简单方法,因此我将尝试使用 JS -> Native 消息来解决该问题。看来我需要使用 WKUserContentController 并实现 WKScriptMessageHandler 协议。 这工作正常,但小技巧。您需要再等待十分之一秒才能获得实际的内容大小。 不,那个解决方案不对。我有 0.1 的延迟,但在某些情况下这还不够。如果你有更多的内容,你必须一次又一次地增加延迟。【参考方案8】:

大多数答案都使用“document.body.offsetHeight”。

这隐藏了正文的最后一个对象。

我通过使用 KVO 观察者来监听 WKWebview “contentSize”中的变化,然后运行以下代码,从而克服了这个问题:

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
    
)

这不是最漂亮的代码,但它对我有用。

【讨论】:

这里的JS +1!它处理一个非常重要的边缘案例!它正确地测量了 webview 的高度,对于以前比其内容短(例如,有一个垂直滚动条)但现在变得更高的 webview。当将设备从纵向旋转到横向并且您的 web 视图突然变宽时,就会发生这种情况。在这种情况下,document.body.scrollOffset 返回之前的(更高的)值,即使内容现在更短(由于增加的宽度)并且在底部留下了很多空白。不过,我不喜欢 KVO 方法。 didFinish 委托方法更简洁。【参考方案9】:

我发现hlung here的答案,如下扩展WKWebView对我来说是最简单和最有效的解决方案:

https://gist.github.com/pkuecuekyan/f70096218a6b969e0249427a7d324f91

他的评论如下:

“很好!对我来说,我没有设置 webView.frame,而是设置了 autolayout intrinsicContentSize。”

他的代码如下:

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


【讨论】:

【参考方案10】:

这是对@IvanMih 答案的轻微修改。对于那些在WKWebview 末尾遇到大空白的人来说,这个解决方案对我来说效果很好:

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

所以基本上不是根据scrollHeight 计算高度,而是使用webView.scrollView.contentSize 计算高度。我确信在某些情况下这会中断,但我认为它对于静态内容以及如果您在无需用户滚动的情况下显示所有内容时效果很好。

【讨论】:

“WKWebView”类型的值没有成员“scrollView”【参考方案11】:

经过大量实验后,我设法找到了一个适合我的解决方案我为 HTML 注入新样式并使用字体大小和高度

Swift 中的代码

1- 给你的 Webview 导航委托

  webView.navigationDelegate = self

2- 在委托扩展中

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
            
 

希望它也适用于你们 ** 请注意,这不是我在 *** 和其他网站中搜索了很多的全部努力,这也是最终与我一起进行大量测试的结果

【讨论】:

不知道为什么,但“webView.frame.size.height = 1”是我的救命稻草【参考方案12】:

使用@Andriy 的回答和this answer 我能够在 WKWebView 中设置获取 contentSize 的高度并更改它的高度。

这里是完整的 swift 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)
        
    

【讨论】:

【参考方案13】:

我在 UITableViewCell 中尝试了 Javascript 版本,它运行良好。但是,如果你想把它放在滚动视图中。我不知道为什么,高度可以更高,但不能更短。但是,我在这里找到了 UIWebView 解决方案。 https://***.com/a/48887971/5514452

它也适用于 WKWebView。我认为问题是因为 WebView 需要重新布局,但不知何故它不会缩小,只能放大。我们需要重新设置高度,它肯定会调整大小。

编辑:我在设置约束后重置了框架高度,因为有时由于将框架高度设置为 0,它无法工作。

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

【讨论】:

在我尝试过的无数解决方案中,self.webView.frame.size.height = 0 是唯一有效的解决方案【参考方案14】:

我尝试了滚动视图 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;
];

【讨论】:

【参考方案15】:

也尝试了不同的方法,最终得出了一个解决方案。结果,我制作了一个自调整大小的 WKWebView,它使 intrinsicContentSize 适应其内容的大小。所以你可以在Auto Layouts中使用它。作为一个例子,我做了一个视图,它可以帮助你在 iOS 应用程序上显示数学公式:https://github.com/Mazorati/SVLatexView

【讨论】:

【参考方案16】:

以下代码对我来说非常适合 webkit 中的任何内容。确保将以下委托添加到您的类:WKNavigationDelegate。

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 可能具有的元素类型。

【讨论】:

【参考方案17】:

我想为上述答案中未提及的特殊情况提供解决方案,如果您在 WKWebView 中使用 自定义字体,可能会发生这种情况。

我尝试了此处解释的所有解决方案,以及其他 *** 问题中提到的许多其他解决方案。没有什么对我来说是 100% 正确的。我总是遇到同样的问题:返回的高度总是比 WkWebView 的实际高度小一点。我尝试了WKNavigationDelegate的方式,我尝试通过将js注入到渲染的HTML中来监听自生事件,但没有成功,所有情况下高度总是错误的。

我学到的第一件事是:在加载 html 并等待完成事件之前,必须将 webview 添加到布局中。如果你尝试以孤立的方式渲染 webview 而不将其添加到布局中,那么高度将非常错误。

奇怪的是,我发现在html渲染之后设置断点,在调用高度评估方法之前,返回的高度是正确的。测量哪个高度(scrollHeight 或 offsetheight)并不重要,两者总是正确的。

这为我指明了正确的方向。结论很明显(虽然我花了很多天调试才意识到):收到didFinishNavigation事件后,或者如果你使用自定义js并监听window.onload事件或类似事件,返回的高度几乎是正确的但不完全,因为渲染还没有完成。

正如here 解释的那样,Firefox、Chrome 和 Safari 在字体应用到文档之前触发 DomContenLoaded 事件(也许在 css 也应用到文档之前? )。就我而言,我使用的是嵌入在我的应用程序中并以经典方式在 HTML 中引用的自定义字体:

 <style>
    @font-face 
        font-family: 'CustomFont';
        src: url('montserrat.ttf');
        format('truetype');
    

    body
        font-family: 'CustomFont';
        font-size: 12px;
    

解决方案? 你必须听事件 document.fonts.ready,发生在事件 window.onload 之后等等。在 WkWebView 中加载的 html 中嵌入以下 js:

    document.fonts.ready.then(function() 
 window.webkit.messageHandlers.iosEventListener.postMessage('custom_event_fonts_ready');
  
);

然后在你的 iOS 应用中,使用

监听事件
  self.webView.configuration.userContentController.add(self, name: "iosEventListener")

收到时

        public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) 
            if let body = message.body as? String 
                if (body == "custom_event_fonts_ready") 
                        self.evaluateBodyHeight()
    
            
        

 private func evaluateBodyHeight() 
        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
//Do something with the height.


                )

            
        )

    

我不确定,但我认为使用此解决方案,所有测量 Web 视图高度的不同方法都将返回正确的方法。经过近一个月的调试和绝望,我不想测试它们

为我的英语不好道歉。

【讨论】:

【参考方案18】:

最好的方法是观察 webView.scrollView 的 contentSize 属性并相应地更新 webView 的高度 constraint

private var contentSizeObserver: NSKeyValueObservation?
    
contentSizeObserver = webView.scrollView.observe(\.contentSize, options: .new)  [weak self] _, change in
    guard let contentSize = change.newValue else  return 
    self?.csWebViewHeight?.update(offset: contentSize.height)


override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) 
    super.viewWillTransition(to: size, with: coordinator)
    // Recalculate webView size
    csWebViewHeight?.update(offset: 0)
    webView.setNeedsLayout()
    webView.layoutIfNeeded()

【讨论】:

以上是关于如何确定 WKWebView 的内容大小?的主要内容,如果未能解决你的问题,请参考以下文章

当 WKWebView 框架调整大小时,内容大小损坏

从 WKWebView 获取所有 cookie

显示带有评论的文章,​​例如 Medium 应用程序

WKWebView+contentInset 使内容大小错误

WKWebView:应用程序因“NSInternalInconsistencyException”异常而崩溃,因为“未调用传递给 X 的完成处理程序”

iOS 13 beta 中 WKWebView 的内在内容大小问题