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

I have a UIWebView with different (single page) content. I'd like to find out the CGSize of the content to resize my parent views appropriately. The obvious -sizeThatFits: unfortunately just returns the current frame size of the webView.

This question is related to ios iphone cocoa-touch uiwebview uikit

The answer is


Xcode8 swift3.1:

  1. Set webView height to 0 first.
  2. In webViewDidFinishLoad Delegate:

let height = webView.scrollView.contentSize.height

Without step1, if webview.height > actual contentHeight, step 2 will return webview.height but not contentsize.height.


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 :

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.

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. With his solution, the desired width is set to the webView's frame before calling sizeThatFits with a CGSizeZero parameter. So I maintain this solution is working on iOS 6 by "chance".

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.

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:

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

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


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

The important tip turned out to be that UIWebView does not report its size correctly until rendered to the screen. 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. This worked for me because I am calling loadHTMLString on local content. If you are using loadRequest you may need to update the contentSize in webViewDidFinishLoad also, depending on how quickly the html is retrieved.

There is no flickering, because only the invisible part of the scroll view is changed.


This's weird!

I tested the solutions both sizeThatFits: and [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight"] are NOT working for me.

However, I found an interesting easy way to get the right height of webpage content. Currently, I used it in my delegate method scrollViewDidScroll:.

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

Verified in iOS 9.3 Simulator/Device, good luck!

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.


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. The issue can be resolved also for width.


Resurrecting this question because I found Ortwin's answer to only work MOST of the time...

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. Then for whatever reason the next call to sizeThatFits when webViewDidFinishLoad fires again will incorrectly return the same value it did before! 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?)

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

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.


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.

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;

    //......
}

AFAIK you can use [webView sizeThatFits:CGSizeZero] to figure out it's content size.


If your HTML contains heavy HTML-contents like iframe's (i.e. facebook-, twitter, instagram-embeds) the real solution is much more difficult, first wrap your 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:

    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:

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

P.S. You can implement time delaying (SetTimeout and setInterval) inside your ObjectiveC/Swift-code - it's up to you.

P.S.S. Important info about UIWebView and Facebook Embeds: Embedded Facebook post does not shows properly in UIWebView


In Xcode 8 and iOS 10 to determine the height of a web view. 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. If there is no JavaScript used this works for sure:

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

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. I found that the disconnected UIWebView doesn't report its size properly with -[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.

If the UIWebView's height is too big (i.e. you have it set to 1000, but the HTML content size is only 500):

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

All return a height of 1000.

To solve my problem in this case, I used https://stackoverflow.com/a/11770883/9636, which I dutifully voted up. However, I only use this solution when my UIWebView.frame.width is the same as the -[UIWebView sizeThatFits:] width.


Also in iOS 7 for proper working of all of mentioned methods add this in your view controller viewDidLoad method:

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

Otherwise neither of methods would work as it should.


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:

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

Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to iphone

Detect if the device is iPhone X Xcode 8 shows error that provisioning profile doesn't include signing certificate Access files in /var/mobile/Containers/Data/Application without jailbreaking iPhone Certificate has either expired or has been revoked Missing Compliance in Status when I add built for internal testing in Test Flight.How to solve? cordova run with ios error .. Error code 65 for command: xcodebuild with args: "Could not find Developer Disk Image" Reason: no suitable image found iPad Multitasking support requires these orientations How to insert new cell into UITableView in Swift

Examples related to cocoa-touch

Include of non-modular header inside framework module Move textfield when keyboard appears swift Create space at the beginning of a UITextField Navigation bar with UIImage for title Generate a UUID on iOS from Swift How do I write a custom init for a UIView subclass in Swift? creating custom tableview cells in swift How would I create a UIAlertView in Swift? Get current NSDate in timestamp format How do you add an in-app purchase to an iOS application?

Examples related to uiwebview

How to display .svg image using swift Can I set the cookies to be used by a WKWebView? How to Migrate to WKWebView? iOS JavaScript bridge How to load local html file into UIWebView Load resources from relative path using local html in uiwebview Clearing UIWebview cache How to determine the content size of a UIWebView? UIWebView open links in Safari Can we open pdf file using UIWebView on iOS?

Examples related to uikit

CGRectMake, CGPointMake, CGSizeMake, CGRectZero, CGPointZero is unavailable in Swift How to trap on UIViewAlertForUnsatisfiableConstraints? Changing Placeholder Text Color with Swift Move view with keyboard using Swift How to use UIVisualEffectView to Blur Image? preferredStatusBarStyle isn't called How to change Navigation Bar color in iOS 7? UICollectionView spacing margins How to programmatically get iOS status bar height Get UIScrollView to scroll to the top