0

This seems like a common problem, but I'm unable find an adequate solution. I'm trying to re-size a bunch of UIWebViews to fit their content, but it doesn't seem to work properly. The best solution I've found so far is this answer, but it only appears to work occasionally in my solution. The idea is to re-size the UIWebView in the webViewDidFinishLoad: delegate method. However, this still doesn't get the right size. If I trigger the update at some undefined time after webViewDidFinishLoad:, the content does get sized correctly.

I will describe my situation below, and I'll paste relevant source on Gist.

Problem Description

I have a situation where I'm getting data from a JSON API and displaying the data in webviews. The API returns an array of objects, and each object has a "content" field with HTML-formatted text. The designers have given a structure which involved a (mostly) hidden view that you swipe up to reveal, which contains a scroll view of all the content, formatted in a very particular way.

To implement this, I have three view controllers: The main view controller, the secondary view controller which you swipe up on the main view controller to reveal, and a tertiary view controller that displays one item of html-formatted content from the JSON API results.
The secondary view controller contains a UIScrollView and an array of tertiary view controllers. Each tertiary view controller contains a UIWebView to display the HTML-formatted content, and labels for displaying other attributes of the JSON object. Because the tertiary views will be displayed inside a UIScrollView in the secondary view, the tertiary views' UIWebView needs to be re-sized to fit its content and have user interaction disabled in order to prevent a confusing scroll-view-in-scroll-view scenario.

The process to get all this to work is as follows:

  1. First, on the Primary controllers ViewDidLoad, load up and display the secondary view and an activity indicator so the UI does not appear to freeze
  2. Next, load the data from the JSON API
  3. When the data from the API has loaded (notified via a delegate), construct tertiary view controllers, set their attributes from the loaded data and add them to the secondary controller's list.
  4. Once that is finished, call the Secondary controller's setup method. This method loops through the list of tertiary controllers and calls their setup methods in turn
  5. Each tertiary controller's setup method sets the text for the labels and sets the content for the UIWebView using loadHTMLString: with a pre-defined html file's content, with the content of the JSON object substituted in.
  6. On the tertiary controller's webViewDidFinishLoad: delegate method, using the method at this answer, re-size the web view based on the content size.
  7. Re-size the tertiary controller's view to fit the web view and position any labels that go underneath the web view
  8. The tertiary controller then calls a delegate method to notify the secondary controller that it has finished loading. The secondary controller keeps tabs on which of its tertiary controllers have finished loading.
  9. When all tertiary controllers are finished loading, the secondary controller loops through its list and adds the tertiary views to it's UIScrollView's content, adjusting frames and the content height as it goes.
Community
  • 1
  • 1
death_au
  • 1,282
  • 2
  • 20
  • 41

2 Answers2

6

If you are using iOS 5.0, you could probably use webview.scrollView.contentsize, otherwise you can also get the content size through javascript.

CGFloat newHeight = [[webView stringByEvaluatingJavaScriptFromString:@"document.body.offsetHeight;"] floatValue];

or alternatively

CGFloat newHeight = [[webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.scrollHeight;"] floatValue];
Steven Veltema
  • 2,140
  • 15
  • 18
  • The javascript code worked. I'd tried some other javascript code preciously with no luck, but `document.body.offsetHeight;` did the trick. The ScrollView's ContentSize property didn't work. I would prefer a native code fix (the javascript seems dodgy IMO) but I'll award you the bounty if no better answers arrive by tomorrow. – death_au Jul 30 '12 at 05:28
  • Thanks, I agree that using javascript is a dirty hack. It would be nice to have something better... – Steven Veltema Jul 30 '12 at 05:33
  • On second look... It *almost* works. It's a lot better than what I had, but occasionally the view will be too small. If I instead use `document.documentElement.scrollHeight;` then it will occasionally be too large. In fact, averaging the two brings me closer, but will still occasionally be too large. There doesn't seem to by any rhyme nor reason as to when the size difference is evident. – death_au Jul 30 '12 at 06:31
  • In fact, adding more content to the web views shows me that neither are correct (and in some cases, neither are large enough). – death_au Jul 30 '12 at 07:11
  • hmm interesting, I've never had problems with this, but then again I have only used rather simple html with minimal inlined css, and no dynamic content added ofter loading. – Steven Veltema Jul 31 '12 at 02:06
  • have you tried getting the union of the subview frames? This gives me the same values as the javascript, but I don't know how well it will work out for your case CGRect contentRect = CGRectZero; for (UIView *view in myWebView.scrollView.subviews) contentRect = CGRectUnion(contentRect, view.frame); – Steven Veltema Jul 31 '12 at 02:17
  • 1
    I believe I may have solved this, and I'm again going back on my previous statements to say that your method of using javascript *did* work. The trick was, when I initialize the web view (which is code I didn't provide on Gist) I set its height to 1. I'm doing this in my `webViewDidFinishLoad:` method, but the trick was to do this on initialization rather than later. Thanks for your help Steven, it pointed me in the right direction. – death_au Jul 31 '12 at 02:26
  • 1
    death_au, that trick of yours on the initialization was huge. I had been debugging this forever. On initialization I was using CGRectZero and I couldn't figure out why these javascript methods weren't working. Thanks! – AbuZubair Oct 07 '13 at 08:25
0

I know you have a solution, but I just worked through this problem myself, and I came up with a simple yet stupid answer.

At first my UIWebView would only grow, but never shrink. So I started by trying to set the frame size, but it would try to scroll on a too small frame, so instead I set the contentSize.height to 1 every time I loaded something into it. My finish load looked something like this

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSLog(@"--- webViewDidFinishLoad (pre sizeToFit) webview.frame = %@", NSStringFromCGRect(_detailsWebView.frame));
    NSLog(@"--- webViewDidFinishLoad (pre sizeToFit) scrollview.contentSize = %@", NSStringFromCGSize(_detailsWebView.scrollView.contentSize));
    [_detailsWebView sizeToFit];
    NSLog(@"--- webViewDidFinishLoad (post sizeToFit) scrollview.contentSize = %@", NSStringFromCGSize(_detailsWebView.scrollView.contentSize));
    NSLog(@"--- webViewDidFinishLoad (post sizeToFit) webview.frame = %@", NSStringFromCGRect(_detailsWebView.frame));
}

but this gave me output like

[8431:707] --- webViewDidFinishLoad (pre sizeToFit) webview.frame = {{20, 11}, {663, 757}}
[8431:707] --- webViewDidFinishLoad (pre sizeToFit) scrollview.contentSize = {663, 1}
[8431:707] --- webViewDidFinishLoad (post sizeToFit) scrollview.contentSize = {663, 92}
[8431:707] --- webViewDidFinishLoad (post sizeToFit) webview.frame = {{20, 11}, {663, 1}}

It now worked as long as my "new" content was bigger than my old content, but if the content shrank, it had issues. It turns out, somehow sizeToFit was updating the frame of the webview before it updated the contentSize

I was able to fix this by manually updating the frame from the contentSize after I called sizeToFit and ultimately, I wound up just calling sizeToFit twice in a row.

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [_detailsWebView sizeToFit];
    [_detailsWebView sizeToFit];
}

And it works. I can't say I'm happy with such a solution and I'm striving to make it better, but it works and the code it extremely simplistic.

DBD
  • 23,075
  • 12
  • 60
  • 84