如何确定UIWebView的内容大小?


Answers:


250

原来,我的第一个猜测-sizeThatFits:并不完全是错误的。似乎可行,但前提是在发送之前将webView的框架设置为最小尺寸-sizeThatFits:。之后,我们可以通过合适的尺寸来校正错误的框架尺寸。这听起来很糟糕,但实际上还不错。由于我们两个帧都紧接彼此更改,因此视图不会更新,也不会闪烁。

当然,我们必须等到内容加载完毕后,才能将代码放入-webViewDidFinishLoad:委托方法中。

对象

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

斯威夫特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
}

我应该指出还有另一种使用JavaScript的方法(感谢@GregInYEG)。不知道哪种解决方案效果更好。

在两种hacky解决方案中,我更喜欢这种解决方案。


我尝试了您的方法。我遇到的问题是没有滚动。有什么建议?
测试2010年

1
我发现滚动问题是由于更改的Webview高度而发生的。我是否需要更改UIView大小?
测试

不知道你的问题是什么。也许您可以使用一些代码或描述来发布新问题?
Ortwin Gentz 2010年

@testing,@Bogatyr:是的,如果您的UIWebView视图高于父视图,则需要将其嵌入UIScrollView
Ortwin Gentz

7
如果给webView.scalesPageToFit = YES; 那么只有此解决方案有效..感谢解决方案。–
SP

92

我有另一个很好的解决方案。

一方面,Ortwin的方法和解决方案仅适用于iOS 6.0及更高版本,但无法在iOS 5.0、5.1和5.1.1上正常工作,另一方面,我有些不喜欢且无法理解的内容使用Ortwin的方法时,将使用[webView sizeThatFits:CGSizeZero]带有参数的方法CGSizeZero:如果您阅读了有关此方法及其参数的Apple官方文档,则说明清楚:

此方法的默认实现返回视图边界矩形的大小部分。子类可以重写此方法以基于任何子视图的所需布局返回自定义值。例如,一个UISwitch对象返回一个固定的大小值,该值表示开关视图的标准大小,而UIImageView对象返回一个其当前显示的图像的大小。

我的意思是,就像他遇到了没有任何逻辑的解决方案一样,因为阅读文档,传递给它的参数[webView sizeThatFits: ...]至少应该具有期望的width。与他的溶液,所需的宽度被设定为webView调用之前的帧sizeThatFitsCGSizeZero参数。因此,我认为此解决方案可以通过“机会”在iOS 6上运行。

我想象过一种更合理的方法,它的优势是可以在iOS 5.0及更高版本上使用...以及在复杂的情况下,多个webView(及其属性webView.scrollView.scrollEnabled = NO嵌入到)中scrollView

这是我的代码,可将的Layout强制webView设置为所需的Layout,width并将对应的heightset返回其webView自身:

对象

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

斯威夫特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;
}

请注意,在我的例子中,webView被嵌入到定制scrollView具有其它webViews......所有这些webViews有他们webView.scrollView.scrollEnabled = NO,并且代码的最后一块,我不得不添加了的计算heightcontentSize我的自定义的scrollView嵌入这些webViews,但它是那么容易用上述技巧将我webViewframe.size.height计算结果相加...


3
这比公认的解决方案要优雅得多,并且它不依赖未记录和未定义的行为。与插入javascript相比,它的黑客行为要少得多。我希望我可以投票赞成64次。
bugloaf 2012年

这正是我试图解释的。谢谢。它的最大优点是,它似乎可以在所有iOS版本下使用。
现象

该解决方案确实看起来不错,但是如果您仍然针对iOS 4.x,请务必小心。-[UIWebView scrollView]仅在iOS 5.0或更高版本中可用。
Ortwin Gentz

2
我尝试了这种方法和类似的解决方案,结果证明,只有在Web视图或其嵌入的视图已经在屏幕上的情况下,此方法才有效。如果在将webview添加到视图层次结构之前尝试此操作,则结果将是手动大小的高度。
2013年

1
另一个解决方案并非每次都有效,不知道为什么,但这可以解决问题!这应该被标记为正确答案。
瓦西里(Vassily)

37

重新提出这个问题,因为我发现Ortwin的答案在大多数情况下只起作用...

webViewDidFinishLoad可能会多次调用该方法,并且返回的第一个值sizeThatFits只是最终大小应占的一部分。然后,不管出于什么原因,以在下次调用sizeThatFitswebViewDidFinishLoad再次开火会错误地返回之前也做了同样的价值!对于相同的内容,这将是随机发生的,就像某种并发问题一样。也许这种行为随着时间的流逝而发生了变化,因为我正在为iOS 5进行构建,并且还发现它的sizeToFit工作方式几乎相同(尽管以前没有这样做?)

我已经解决了这个简单的解决方案:

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

迅捷(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
    }
}

更新:我发现如评论中所述,这似乎并没有解决内容缩小的情况。不确定所有内容和操作系统版本是否正确,请尝试一下。


您是否在网页中包含图片或其他资产?这可能会导致代理的其他解雇。除此之外,您的解决方案仅在您不提供webView.scalesPageToFit = YES上述评论中提到的@Sijo时有效。
奥特温·根茨

是的,可能就是这样-您的解决方案在纯文本情况下可以正常工作。但是sizeThatFits在最终的委托调用期间没有给出正确的值是个问题……很奇怪。我们怎么可能知道哪个呼叫将是最后一个呼叫,因此在那之前我们不尝试使用sizeThatFits?
基兰·哈珀

如果在第一个委托呼叫中您从sizeThatFits获得了正确的结果,您能否不忽略随后的任何呼叫?
奥特温·根茨

是的,但是反之亦然:)第一次委托调用可能导致错误的sizeThatFits,然后该值仍然存在。好像sizeThatFits以某种方式修改了Web视图,以便任何进一步的sizeThatFits调用都可以得到相同的结果,或者是框架的设置(仍在加载时这样做)。
基兰·哈珀

1
此解决方案也无法正常工作。设置较大的页面,然后设置较小的页面时,将始终返回较大的值。似乎“懒惰”地扩展了内部视图。有任何解决方法吗?
Legoless

23

在Xcode 8和iOS 10中确定Web视图的高度。你可以用

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

或为斯威夫特

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

1
尝试查找使用webview内容的大小。.myWebView.scrollView.contentSize.height
Hardik Thakkar,

8

一个简单的解决方案是只使用,webView.scrollView.contentSize但我不知道这是否适用于JavaScript。如果没有使用JavaScript,则可以肯定地起作用:

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

3

您可以使用AFAIK [webView sizeThatFits:CGSizeZero]来确定其内容大小。


3

真奇怪

我测试的解决方案,既sizeThatFits:[webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"]为我工作。

但是,我找到了一种有趣的简单方法来获得正确的网页内容高度。目前,我在委托方法中使用了它scrollViewDidScroll:

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

已在iOS 9.3模拟器/设备中验证,祝您好运!

编辑:

背景:html内容是由我的字符串变量和HTTP内容模板计算的,并由method加载的loadHTMLString:baseURL:,那里没有注册的JS脚本。


3

对于iOS10,我得到的值为0(零),document.height因此document.body.scrollHeight获取Webview中文档高度的解决方案也是。这个问题也可以解决width


2

我使用的UIWebView不是子视图(因此也不是窗口层次结构的一部分)来确定的HTML内容的大小UITableViewCells。我发现断开连接后UIWebView无法使用正确报告其大小-[UIWebView sizeThatFits:]。另外,如https://stackoverflow.com/a/3937599/9636中所述,您必须将UIWebViews 设置frame height为1才能获得正确的高度。

如果UIWebView的高度太大(即您将其设置为1000,但是HTML内容大小仅为500):

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

全部返回height1000。

为了解决这种情况下的问题,我使用了https://stackoverflow.com/a/11770883/9636,我已将其投了赞成票。但是,仅当我UIWebView.frame.width与相同时,才使用此解决方案-[UIWebView sizeThatFits:] width


1
我已经尝试了该线程中的所有示例,使用“ document.height”检索文档的高度是最可靠的。可以在容器(例如DIV)外部添加其他HTML,这会使getElementById(...)在我执行的所有测试中都不可靠。document.height有效。
约翰·罗杰斯

2

这里的建议都没有帮助我解决我的情况,但是我读到的东西确实给了我答案。我有一个ViewController,带有一组固定的UI控件,然后是UIWebView。我希望整个页面都可以滚动,就像UI控件已连接到HTML内容一样,因此我禁用了UIWebView上的滚动,然后必须正确设置父级滚动视图的内容大小。

重要的提示是UIWebView在呈现到屏幕之前无法正确报告其大小。因此,当我加载内容时,我将内容大小设置为可用的屏幕高度。然后,在viewDidAppear中,将scrollview的内容大小更新为正确的值。这对我有用,因为我正在本地内容上调用loadHTMLString。如果您正在使用loadRequest,则可能还需要更新webViewDidFinishLoad中的contentSize,具体取决于html检索的速度。

没有闪烁,因为只更改了滚动视图的不可见部分。


我担心Web视图加载得足够快以至于它在viewDidAppear中完成是纯粹的运气。可能只有在视图显示为动画的情况下才起作用,因此动画的时间足够长以加载内容。我宁愿依靠它webViewDidFinishLoad作为安全的方法。
Ortwin Gentz 2014年

2

如果您的HTML包含大量的 HTML内容(例如iframe)(例如,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>"];

然后将处理x-update-webview-height -scheme添加到shouldStartLoadWithRequest中

    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;
    }
    ...

最后,在您的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您可以在ObjectiveC / Swift代码中实现时间延迟(SetTimeout和setInterval),具体取决于您。

PSS有关UIWebView和Facebook Embeds的重要信息:嵌入式Facebook帖子未在UIWebView中正确显示


这是有趣的!您能否描述脚本部分的功能以及在需要时如何进行调整?看起来它会定期检查高度,但是涉及多个时间间隔吗?
基兰·哈珀

@KieranHarper,您到底需要描述什么?
MingalevME

2

当使用webview作为滚动视图中某处的子视图时,可以将高度约束设置为某个常数值,然后从其输出插座,然后像这样使用它:

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

1

我也遇到了这个问题,然后我意识到,如果我想计算webView的动态高度,我需要先告诉webView的宽度,因此我在js之前添加了一行,结果我可以非常准确的实际高度。

代码很简单,像这样:

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

    //......
}

1

Xcode8 swift3.1:

  1. 首先将webView的高度设置为0
  2. webViewDidFinishLoad代表中:

let height = webView.scrollView.contentSize.height

如果没有步骤1,则如果webview.height>实际contentHeight,则步骤2将返回webview.height,但不会返回contentsize.height。


这就是为什么我无法获得所需帧的原因..赞成
NSPratik

0

同样在iOS 7中,为使所有提到的方法正常工作,请在您的视图控制器viewDidLoad方法中添加以下内容:

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

否则,这两种方法都无法正常工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.