简体   繁体   English

如何确定UIWebView的内容大小?

[英]How to determine the content size of a UIWebView?

I have a UIWebView with different (single page) content. 我有一个具有不同(单页)内容的UIWebView I'd like to find out the CGSize of the content to resize my parent views appropriately. 我想找出内容的CGSize来适当调整父视图的大小。 The obvious -sizeThatFits: unfortunately just returns the current frame size of the webView. 显而易见的-sizeThatFits:遗憾的是只返回webView的当前帧大小。

It turned out that my first guess using -sizeThatFits: was not completely wrong. 事实证明,我使用-sizeThatFits:第一次猜测并非完全错误。 It seems to work, but only if the frame of the webView is set to a minimal size prior to sending -sizeThatFits: . 它似乎工作,但只有在发送-sizeThatFits:之前,webView的框架设置为最小尺寸-sizeThatFits: After that we can correct the wrong frame size by the fitting size. 之后,我们可以通过拟合尺寸校正错误的框架尺寸。 This sounds terrible but it's actually not that bad. 这听起来很可怕,但实际上并没有那么糟糕。 Since we do both frame changes right after each other, the view isn't updated and doesn't flicker. 由于我们彼此之后都进行了两次帧更改,因此视图不会更新且不会闪烁。

Of course, we have to wait until the content has been loaded, so we put the code into the -webViewDidFinishLoad: delegate method. 当然,我们必须等到内容加载完毕,因此我们将代码放入-webViewDidFinishLoad: delegate方法。

Obj-C OBJ-C

- (void)webViewDidFinishLoad:(UIWebView *)aWebView {
    CGRect frame = aWebView.frame;
    frame.size.height = 1;
    aWebView.frame = frame;
    CGSize fittingSize = [aWebView sizeThatFits:CGSizeZero];
    frame.size = fittingSize;
    aWebView.frame = frame;

    NSLog(@"size: %f, %f", fittingSize.width, fittingSize.height);
}

Swift 4.x Swift 4.x

func webViewDidFinishLoad(_ webView: UIWebView) {
    var frame = webView.frame
    frame.size.height = 1
    webView.frame = frame
    let fittingSize = webView.sizeThatFits(CGSize.init(width: 0, height: 0))
    frame.size = fittingSize
    webView.frame = frame
}

I should point out there's another approach (thanks @GregInYEG) using JavaScript. 我应该指出使用JavaScript的另一种方法 (感谢@GregInYEG)。 Not sure which solution performs better. 不确定哪种解决方案表现更好。

Of two hacky solutions I like this one better. 在两个hacky解决方案中,我更喜欢这个。

I have another solution that works great. 我有另一个很好的解决方案。

On one hand, Ortwin's approach & solution works only with iOS 6.0 and later, but fails to work correctly on iOS 5.0, 5.1 and 5.1.1, and on the other hand there is something that I don't like and can't understand with Ortwin's approach, it's the use of the method [webView sizeThatFits:CGSizeZero] with the parameter CGSizeZero : If you read Apple Official documentation about this methods and its parameter, it says clearly : 一方面,Ortwin的方法和解决方案仅适用于iOS 6.0及更高版本,但无法在iOS 5.0,5.1和5.1.1上正常工作,另一方面,有些东西我不喜欢也无法理解使用Ortwin的方法,使用方法[webView sizeThatFits:CGSizeZero]和参数CGSizeZero :如果您阅读Apple官方文档有关此方法及其参数,它会清楚地说:

The default implementation of this method returns the size portion of the view's bounds rectangle. 此方法的默认实现返回视图边界矩形的大小部分。 Subclasses can override this method to return a custom value based on the desired layout of any subviews. 子类可以覆盖此方法,以根据所需子视图的所需布局返回自定义值。 For example, a UISwitch object returns a fixed size value that represents the standard size of a switch view, and a UIImageView object returns the size of the image it is currently displaying. 例如,UISwitch对象返回一个固定大小的值,表示切换视图的标准大小,UIImageView对象返回当前显示的图像的大小。

What I mean is that it's like he came across his solution without any logic, because reading the documentation, the parameter passed to [webView sizeThatFits: ...] should at least have the desired width . 我的意思是,就像他在没有任何逻辑的情况下遇到他的解决方案一样,因为阅读文档,传递给[webView sizeThatFits: ...]的参数至少应该具有所需的width With his solution, the desired width is set to the webView 's frame before calling sizeThatFits with a CGSizeZero parameter. 使用他的解决方案,在使用CGSizeZero参数调用sizeThatFits之前,将所需宽度设置为webView的框架。 So I maintain this solution is working on iOS 6 by "chance". 所以我认为这个解决方案是通过“偶然”在iOS 6上运行的。

I imagined a more rational approach, which has the advantage of working for iOS 5.0 and later... And also in complex situations where more than one webView (With its property webView.scrollView.scrollEnabled = NO is embedded in a scrollView . 我想象一种更合理的方法,它具有适用于iOS 5.0及更高版本的优势......而且在复杂的情况下,其中多个webView(其属性webView.scrollView.scrollEnabled = NO嵌入在scrollView

Here is my code to force the Layout of the webView to the desired width and get the corresponding height set back to the webView itself: 这是我的代码,强制webView的布局到所需的width并获得相应的height设置回webView本身:

Obj-C OBJ-C

- (void)webViewDidFinishLoad:(UIWebView *)aWebView
{   
    aWebView.scrollView.scrollEnabled = NO;    // Property available in iOS 5.0 and later 
    CGRect frame = aWebView.frame;

    frame.size.width = 200;       // Your desired width here.
    frame.size.height = 1;        // Set the height to a small one.

    aWebView.frame = frame;       // Set webView's Frame, forcing the Layout of its embedded scrollView with current Frame's constraints (Width set above).

    frame.size.height = aWebView.scrollView.contentSize.height;  // Get the corresponding height from the webView's embedded scrollView.

    aWebView.frame = frame;       // Set the scrollView contentHeight back to the frame itself.
}

Swift 4.x Swift 4.x

func webViewDidFinishLoad(_ aWebView: UIWebView) {

    aWebView.scrollView.isScrollEnabled = false
    var frame = aWebView.frame

    frame.size.width = 200
    frame.size.height = 1

    aWebView.frame = frame
    frame.size.height = aWebView.scrollView.contentSize.height

    aWebView.frame = frame;
}

Note that in my example, the webView was embedded in a custom scrollView having other webViews ... All these webViews had their webView.scrollView.scrollEnabled = NO , and the last piece of code I had to add was the calculation of the height of the contentSize of my custom scrollView embedding these webViews , but it was as easy as summing my webView 's frame.size.height computed with the trick described above... 请注意,在我的例子中, webView是嵌入在一个自定义的scrollView具有其它webViews ......所有这些webViews有其webView.scrollView.scrollEnabled = NO ,和代码的最后一块,我不得不添加是计算height的我的自定义scrollViewcontentSize嵌入这些webViews ,但它就像使用上述技巧计算我的webViewframe.size.height一样简单...

Resurrecting this question because I found Ortwin's answer to only work MOST of the time... 复活这个问题是因为我发现Ortwin的答案只适用于大部分时间......

The webViewDidFinishLoad method may be called more than once, and the first value returned by sizeThatFits is only some portion of what the final size should be. webViewDidFinishLoad方法可能会被多次调用,而sizeThatFits返回的第一个值只是最终大小应该是的一部分。 Then for whatever reason the next call to sizeThatFits when webViewDidFinishLoad fires again will incorrectly return the same value it did before! 然后无论出于何种原因,当webViewDidFinishLoad再次触发时,下一次调用sizeThatFits将错误地返回之前执行的相同值! This will happen randomly for the same content as if it's some kind of concurrency problem. 对于相同的内容,这将是随机发生的,就好像它是某种并发问题一样。 Maybe this behaviour has changed over time, because I'm building for iOS 5 and have also found that sizeToFit works in much the same way (although previously this didn't?) 也许这种行为随着时间的推移而发生了变化,因为我正在为iOS 5构建并且还发现sizeToFit工作方式大致相同(虽然以前这不是吗?)

I have settled on this simple solution: 我已经解决了这个简单的解决方案:

- (void)webViewDidFinishLoad:(UIWebView *)aWebView
{        
    CGFloat height = [[aWebView stringByEvaluatingJavaScriptFromString:@"document.height"] floatValue];
    CGFloat width = [[aWebView stringByEvaluatingJavaScriptFromString:@"document.width"] floatValue];
    CGRect frame = aWebView.frame;
    frame.size.height = height;
    frame.size.width = width;
    aWebView.frame = frame;
}

Swift (2.2): 斯威夫特(2.2):

func webViewDidFinishLoad(webView: UIWebView) {

    if let heightString = webView.stringByEvaluatingJavaScriptFromString("document.height"),
        widthString = webView.stringByEvaluatingJavaScriptFromString("document.width"),
        height = Float(heightString),
        width = Float(widthString) {

        var rect = webView.frame
        rect.size.height = CGFloat(height)
        rect.size.width = CGFloat(width)
        webView.frame = rect
    }
}

Update: I have found as mentioned in the comments this doesn't seem to catch the case where the content has shrunk. 更新:我发现在评论中提到这似乎没有抓住内容缩小的情况。 Not sure if it's true for all content and OS version, give it a try. 不确定所有内容和操作系统版本是否属实,请试一试。

In Xcode 8 and iOS 10 to determine the height of a web view. 在Xcode 8和iOS 10中确定Web视图的高度。 you can get height using 你可以使用高度

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    CGFloat height = [[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] floatValue];
    NSLog(@"Webview height is:: %f", height);
}

OR for Swift 或者对于斯威夫特

 func webViewDidFinishLoad(aWebView:UIWebView){
        let height: Float = (aWebView.stringByEvaluatingJavaScriptFromString("document.body.scrollHeight")?.toFloat())!
        print("Webview height is::\(height)")
    }

A simple solution would be to just use webView.scrollView.contentSize but I don't know if this works with JavaScript. 一个简单的解决方案是使用webView.scrollView.contentSize但我不知道这是否适用于JavaScript。 If there is no JavaScript used this works for sure: 如果没有使用JavaScript,这肯定有效:

- (void)webViewDidFinishLoad:(UIWebView *)aWebView {
    CGSize contentSize = aWebView.scrollView.contentSize;
    NSLog(@"webView contentSize: %@", NSStringFromCGSize(contentSize));
}

AFAIK你可以使用[webView sizeThatFits:CGSizeZero]来计算它的内容大小。

This's weird! 这太奇怪了!

I tested the solutions both sizeThatFits: and [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] are NOT working for me. 我测试了sizeThatFits:的解决方案sizeThatFits:[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] 适合我。

However, I found an interesting easy way to get the right height of webpage content. 但是,我发现了一种有趣的方法来获得正确的网页内容。 Currently, I used it in my delegate method scrollViewDidScroll: . 目前,我在我的委托方法scrollViewDidScroll:使用它。

CGFloat contentHeight = scrollView.contentSize.height - CGRectGetHeight(scrollView.frame);

Verified in iOS 9.3 Simulator/Device, good luck! 在iOS 9.3模拟器/设备中验证,祝您好运!

EDIT: 编辑:

Background: The html content is calculated by my string variable and HTTP content template, loaded by method loadHTMLString:baseURL: , no registered JS scripts there. 背景:html内容由我的字符串变量和HTTP内容模板计算,由方法loadHTMLString:baseURL:加载,没有注册的JS脚本。

For iOS10, I was getting 0 (zero) value of document.height so document.body.scrollHeight is the solution to get height of document in Webview. 对于iOS10,我获得了document.height 0(零)值,因此document.body.scrollHeight是获取Webview中文档高度的解决方案。 The issue can be resolved also for width . 对于width也可以解决该问题。

I'm using a UIWebView that isn't a subview (and thus isn't part of the window hierarchy) to determine the sizes of HTML content for UITableViewCells . 我正在使用不是子视图的UIWebView (因此不是窗口层次结构的一部分)来确定UITableViewCells的HTML内容的大小。 I found that the disconnected UIWebView doesn't report its size properly with -[UIWebView sizeThatFits:] . 我发现断开连接的UIWebView没有正确报告其大小-[UIWebView sizeThatFits:] Additionally, as mentioned in https://stackoverflow.com/a/3937599/9636 , you must set the UIWebView 's frame height to 1 in order to get the proper height at all. 此外,如https://stackoverflow.com/a/3937599/9636中所述 ,您必须将UIWebViewframe height为1才能获得正确的高度。

If the UIWebView 's height is too big (ie you have it set to 1000, but the HTML content size is only 500): 如果UIWebView的高度太大(即您将其设置为1000,但HTML内容大小仅为500):

UIWebView.scrollView.contentSize.height
-[UIWebView stringByEvaluatingJavaScriptFromString:@"document.height"]
-[UIWebView sizeThatFits:]

All return a height of 1000. 全部返回1000的height

To solve my problem in this case, I used https://stackoverflow.com/a/11770883/9636 , which I dutifully voted up. 为了解决我在这种情况下的问题,我使用了https://stackoverflow.com/a/11770883/9636 ,我尽职尽责地投票。 However, I only use this solution when my UIWebView.frame.width is the same as the -[UIWebView sizeThatFits:] width . 但是,当我的UIWebView.frame.width-[UIWebView sizeThatFits:] width同时,我只使用此解决方案。

None of the suggestions here helped me with my situation, but I read something that did give me an answer. 这里的建议都没有帮助我解决我的情况,但我读了一些确实给我答案的东西。 I have a ViewController with a fixed set of UI controls followed by a UIWebView. 我有一个ViewController,其中包含一组固定的UI控件,后跟一个UIWebView。 I wanted the entire page to scroll as though the UI controls were connected to the HTML content, so I disable scrolling on the UIWebView and must then set the content size of a parent scroll view correctly. 我希望整个页面滚动,好像UI控件已连接到HTML内容,因此我禁用UIWebView上的滚动,然后必须正确设置父滚动视图的内容大小。

The important tip turned out to be that UIWebView does not report its size correctly until rendered to the screen. 重要提示原来是UIWebView在呈现到屏幕之前不会正确报告其大小。 So when I load the content I set the content size to the available screen height. 因此,当我加载内容时,我将内容大小设置为可用的屏幕高度。 Then, in viewDidAppear I update the scrollview's content size to the correct value. 然后,在viewDidAppear中,我将scrollview的内容大小更新为正确的值。 This worked for me because I am calling loadHTMLString on local content. 这对我有用,因为我在本地内容上调用loadHTMLString。 If you are using loadRequest you may need to update the contentSize in webViewDidFinishLoad also, depending on how quickly the html is retrieved. 如果您正在使用loadRequest,则可能还需要更新webViewDidFinishLoad中的contentSize,具体取决于检索html的速度。

There is no flickering, because only the invisible part of the scroll view is changed. 没有闪烁,因为只有滚动视图的不可见部分被更改。

If your HTML contains heavy HTML-contents like iframe's (ie facebook-, twitter, instagram-embeds) the real solution is much more difficult, first wrap your HTML: 如果你的HTML包含像iframe这样的重量级 HTML内容(即facebook-,twitter,instagram-embeds),那么真正的解决方案要困难得多,首先包装你的HTML:

[htmlContent appendFormat:@"<html>", [[LocalizationStore instance] currentTextDir], [[LocalizationStore instance] currentLang]];
[htmlContent appendFormat:@"<head>"];
[htmlContent appendString:@"<script type=\"text/javascript\">"];
[htmlContent appendFormat:@"    var lastHeight = 0;"];
[htmlContent appendFormat:@"    function updateHeight() { var h = document.getElementById('content').offsetHeight; if (lastHeight != h) { lastHeight = h; window.location.href = \"x-update-webview-height://\" + h } }"];
[htmlContent appendFormat:@"    window.onload = function() {"];
[htmlContent appendFormat:@"        setTimeout(updateHeight, 1000);"];
[htmlContent appendFormat:@"        setTimeout(updateHeight, 3000);"];
[htmlContent appendFormat:@"        if (window.intervalId) { clearInterval(window.intervalId); }"];
[htmlContent appendFormat:@"        window.intervalId = setInterval(updateHeight, 5000);"];
[htmlContent appendFormat:@"        setTimeout(function(){ clearInterval(window.intervalId); window.intervalId = null; }, 30000);"];
[htmlContent appendFormat:@"    };"];
[htmlContent appendFormat:@"</script>"];
[htmlContent appendFormat:@"..."]; // Rest of your HTML <head>-section
[htmlContent appendFormat:@"</head>"];
[htmlContent appendFormat:@"<body>"];
[htmlContent appendFormat:@"<div id=\"content\">"]; // !important https://stackoverflow.com/a/8031442/1046909
[htmlContent appendFormat:@"..."]; // Your HTML-content
[htmlContent appendFormat:@"</div>"]; // </div id="content">
[htmlContent appendFormat:@"</body>"];
[htmlContent appendFormat:@"</html>"];

Then add handling x-update-webview-height -scheme into your shouldStartLoadWithRequest : 然后在你的shouldStartLoadWithRequest中添加处理x-update-webview-height -scheme

    if (navigationType == UIWebViewNavigationTypeLinkClicked || navigationType == UIWebViewNavigationTypeOther) {
    // Handling Custom URL Scheme
    if([[[request URL] scheme] isEqualToString:@"x-update-webview-height"]) {
        NSInteger currentWebViewHeight = [[[request URL] host] intValue];
        if (_lastWebViewHeight != currentWebViewHeight) {
            _lastWebViewHeight = currentWebViewHeight; // class property
            _realWebViewHeight = currentWebViewHeight; // class property
            [self layoutSubviews];
        }
        return NO;
    }
    ...

And finally add the following code inside your layoutSubviews : 最后在layoutSubviews中添加以下代码:

    ...
    NSInteger webViewHeight = 0;

    if (_realWebViewHeight > 0) {
        webViewHeight = _realWebViewHeight;
        _realWebViewHeight = 0;
    } else {
        webViewHeight = [[webView stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"content\").offsetHeight;"] integerValue];
    }

    upateWebViewHeightTheWayYorLike(webViewHeight);// Now your have real WebViewHeight so you can update your webview height you like.
    ...

PS You can implement time delaying (SetTimeout and setInterval) inside your ObjectiveC/Swift-code - it's up to you. PS你可以在ObjectiveC / Swift代码中实现时间延迟(SetTimeout和setInterval) - 这取决于你。

PSS Important info about UIWebView and Facebook Embeds: Embedded Facebook post does not shows properly in UIWebView PSS关于UIWebView和Facebook嵌入的重要信息: 嵌入式Facebook帖子在UIWebView中无法正确显示

When using webview as a subview somewhere in scrollview, you can set height constraint to some constant value and later make outlet from it and use it like: 当使用webview作为scrollview中的某个子视图时,您可以将高度约束设置为某个常量值,然后从中创建出口并使用它,如:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    webView.scrollView.scrollEnabled = NO;
    _webViewHeight.constant = webView.scrollView.contentSize.height;
}

I'm also stuck on this problem, then I realized that if I want to calculate the dynamic height of the webView, I need to tell the width of the webView first, so I add one line before js and it turns out I can get very accurate actual height. 我也坚持这个问题,然后我意识到,如果我想计算webView的动态高度,我需要先告诉webView的宽度,所以我在js之前添加一行,结果我得到了非常准确的实际高度。

The code is simple like this: 代码很简单,如下所示:

-(void)webViewDidFinishLoad:(UIWebView *)webView
{

    //tell the width first
    webView.width = [UIScreen mainScreen].bounds.size.width;

    //use js to get height dynamically
    CGFloat scrollSizeHeight = [[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] floatValue];
    webView.height = scrollSizeHeight;

    webView.x = 0;
    webView.y = 0;

    //......
}

Xcode8 swift3.1: Xcode8 swift3.1:

  1. Set webView height to 0 first. 首先将webView高度设置为0
  2. In webViewDidFinishLoad Delegate: webViewDidFinishLoad委托中:

let height = webView.scrollView.contentSize.height

Without step1, if webview.height > actual contentHeight, step 2 will return webview.height but not contentsize.height. 如果没有step1,如果webview.height>实际contentHeight,则步骤2将返回webview.height但不返回contentsize.height。

Also in iOS 7 for proper working of all of mentioned methods add this in your view controller viewDidLoad method: 同样在iOS 7中正确处理所有提到的方法,在视图控制器viewDidLoad方法中添加:

if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) {
    self.automaticallyAdjustsScrollViewInsets = NO;
}

Otherwise neither of methods would work as it should. 否则两种方法都不会起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM