views:

5793

answers:

8

I am trying to create a UITableView with variable height rows as explained in the answer to this question

My problem is each cell contains a UIWebView with different (statically loaded) content I can't figure out how to calculate the proper height based on the content. Is there a way to do this? I've tried things like this:

   (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
       WebViewCell *cell = (WebViewCell*)[self tableView:tableView cellForRowAtIndexPath:indexPath];
       [cell setNeedsLayout];
       [cell layoutIfNeeded];
       return cell.bounds.size.height;
    }

The cells themselves are loaded from a nib, which is simply a UITableViewCell containing a UIWebView. (It would also be fine if the cells just adjusted themselves to the largest of the html content, though variable height would be nicer).

+10  A: 

This code is probably too slow for table view use, but does the trick. It doesn't look like there's any alternative, as UIWebView offers no direct access to the DOM.

In a view controller's viewDidLoad I load some HTML in a webview and when the load is finished run some javascript to return the element height.

- (void)viewDidLoad
{
    [super viewDidLoad];
    webview.delegate = self;
    [webview loadHTMLString:@"<div id='foo' style='background: red'>The quick brown fox jumped over the lazy dog.</div>" baseURL:nil];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *output = [webview stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"foo\").offsetHeight;"];
    NSLog(@"height: %@", output);
}
duncanwilcox
Sounds like it can be made work, however I've tried this in the table view and it returns an empty string (I guess because the webview had not finished loading). I'm thinking I could precompute this with an offscreen webview perhaps and cache the results for the tableview.
frankodwyer
Are you measuring the height in the webViewDidFinishLoad? That's the delegate callback indicating that load is complete. It shouldn't return an empty string.
duncanwilcox
This is almost exactly what I do. Although I am having a problem with the fact that the webview can't return its size until it has finished loading, which it might not have when the table needs to know its size. Does anyone have an idea of how to solve this?
Erik B
I used instead: NSString *output = [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"];
BadPirate
+1  A: 

Hi everyone,

I was very glad to find your answers, the javascript trick worked well for me.

However, I wanted to say that there is also the "sizeThatFits:" method :

CGSize goodSize = [webView sizeThatFits:CGSizeMake(anyWidth,anyHeigth)];

We have to set a preferred size different from zero, but apart from that, it usually always works !

Usually, because with the UIWebViews, it seems to work only on the second loading (I use the "loadHTMLString:" method, and yes I call "sizeThatFits:" from the delegate "didFinishLoad:" method).

So, that's why I was very happy to find your solution which works in any case.

Unfalkster
A: 

Did you get sizeThatFits to work with SDK 2? Im trying to use it but it only works correctly with SDK 3.

Thanks Hami.

Hamiseixas
If you want to ask a followup question you probably should not post it as an answer, but as a new question. In the upper right there is a "Ask Question" button that allows you to ask a new question.
sth
+2  A: 

This seems to work. For now.

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    CGFloat webViewHeight = 0.0f;
    if (self.subviews.count > 0) {
        UIView *scrollerView = [self.subviews objectAtIndex:0];
        if (scrollerView.subviews.count > 0) {
            UIView *webDocView = scrollerView.subviews.lastObject;
            if ([webDocView isKindOfClass:[NSClassFromString(@"UIWebDocumentView") class]])
                webViewHeight = webDocView.frame.size.height;
        }
    }
}

It should safely return 0 if it fails.

Dave Batton
How is this better than: [webview stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"foo\").offsetHeight;"];? The problem is that the webview can't return its size until it has finished loading, which it might not have when the table needs to know its size.
Erik B
+1  A: 

Note, that userInteractionEnabled of the UIWebView has to be YES in order to receive any valid height values from any of the methods described above.

aldi
A: 

If you have bad values with sizeThatFits the first time but not after, here a possible cause I've encountered : a bug with external CSS. It seems webViewDidFinisLoad is called to soon, before external CSS are retrieved, and the web document fully built. I think the bug disappears if you reload because of the cache.

A trick : Preload your CSS with a dummy UIWebView soon in your application.

SDK : IOS4

Kabhal
A: 

The problem with -sizeThatFits: is that it doesn't work out of the box, at least not on iOS 4.1. It just returns the current size of the webView (no matter whether it's called with CGSizeZero or an arbitrary non-zero size).

I found out that it actually works if you reduce the frame height of the webView prior to calling -sizeThatFits:.

See my solution: http://stackoverflow.com/questions/3936041/how-to-determine-the-content-size-of-a-uiwebview

Ortwin Gentz
A: 

I had the same problem as I wanted to fit the height of a UIWebView to its contents, but the above JavaScript solution didn't work.

After playing around with it for quite some time, I found that insted of using

[webview stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"foo\").offsetHeight;"],

the simple [webView stringByEvaluatingJavaScriptFromString:@"document.height;"]worked!

@aldi: I tried it with [webView setUserInteractionEnabled:NO] and it worked, too.


EDIT: Sorry, I was wrong (and there's no way to delete de post :-)). document.height gets the height of the UIWebView, not its contents...

keyser