Uiwebview Dynamic Content Size

UIWebView dynamic content size

This post has been updated for Swift 5 & WKWebView


So this is a really great function you wrote there, OP!

Here is just a shorter, more elegant version of your code:

// make sure to declare the delegate when creating your webView (add UIWebViewDelegate to class declaration as well)
myWebView.delegate = self

func webViewDidFinishLoad(webView: UIWebView) {
webView.frame.size.height = 1
webView.frame.size = webView.sizeThatFits(CGSize.zero)
}

Migrating to WKWebView

1) import WebKit

2) make your ViewController inherit from WKNavigationDelegate

3) hook up the WKWebView’s delegate: webView.navigationDelegate = self

4) implement the following protocol function:

webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)

After migrating from UIWebView to WKWebView, above approach doesn’t seem to work anymore.

What you can do instead, is change the line with webView.sizeThatFits(CGSize.zero) to:

webView.frame.size = webView.scrollView.contentSize

The full code for WKWebView would then be:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.frame.size.height = 1
webView.frame.size = webView.scrollView.contentSize
}

How to determine the content size of a WKWebView?

I think I read every answer on this subject and all I had was part of the solution. Most of the time I spent trying to implement KVO method as described by @davew, which occasionally worked, but most of the time left a white space under the content of a WKWebView container. I also implemented @David Beck suggestion and made the container height to be 0 thus avoiding the possibility that the problem occurs if the container height is larger that that of the content. In spite of that I had that occasional blank space.
So, for me, "contentSize" observer had a lot of flaws. I do not have a lot of experience with web technologies so I cannot answer what was the problem with this solution, but i saw that if I only print height in the console but do not do anything with it (eg. resize the constraints), it jumps to some number (e.g. 5000) and than goes to the number before that highest one (e.g. 2500 - which turns out to be the correct one). If I do set the height constraint to the height which I get from "contentSize" it sets itself to the highest number it gets and never gets resized to the correct one - which is, again, mentioned by @David Beck comment.

After lots of experiments I've managed to find a solution that works for me:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
self.containerHeight.constant = height as! CGFloat
})
}

})
}

Of course, it is important to set the constraints correctly so that scrollView resizes according to the containerHeight constraint.

As it turns out didFinish navigation method never gets called when I wanted, but having set document.readyState step, the next one (document.body.offsetHeight) gets called at the right moment, returning me the right number for height.

UIWebView : Get Webview Content size?

When font change at that time change Webview height to 1 than get proper offsetHeight for Webview

webView1.frame.size.height = 1
let size : String = self.webView1.stringByEvaluatingJavaScript(from: "document.documentElement.offsetHeight")!

How to determine the content size of a UIWebView?

It turned out that my first guess using -sizeThatFits: was not completely wrong. It seems to work, but only if the frame of the webView is set to a minimal size prior to sending -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.

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

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. Not sure which solution performs better.

Of two hacky solutions I like this one better.

Make a UIWebView as height as its content with Auto Layout

To make this work you have to do the following steps:

  1. Connect the UIWebView from the nib to an outlet in your view controller
  2. Disable scrolling in the web view
  3. Set the constraints on the UIScrollView, the UIView on top of the web view (In my example I omitted all the labels in that view) and the UIWebView.
  4. Connect the UIWebView's height constraint to an outlet in your view controller.
  5. Set the view controller as UIWebViewDelegate
  6. In webViewDidFinishLoad set the height constraint's constant to the height of the contentSize of the scroll view inside the web view.
  7. Start Key-Value Observing on the contentSize to change the height, when height of the web view has to change because segments of the webpage change their size without reloading the page (like accordeons, or menus).

I won't explain the constraints in detail as you seem to already have figured them out yourself. Here is a screenshot of the constraints:

Sample Image

So, here is the code:

import UIKit

var MyObservationContext = 0

class ViewController: UIViewController {

@IBOutlet weak var webview: UIWebView!
@IBOutlet weak var webviewHeightConstraint: NSLayoutConstraint!
var observing = false

override func viewDidLoad() {
super.viewDidLoad()
webview.scrollView.scrollEnabled = false
webview.delegate = self
webview.loadRequest(NSURLRequest(URL: NSURL(string: "https://www.google.de/intl/de/policies/terms/regional.html")!))
}

deinit {
stopObservingHeight()
}

func startObservingHeight() {
let options = NSKeyValueObservingOptions([.New])
webview.scrollView.addObserver(self, forKeyPath: "contentSize", options: options, context: &MyObservationContext)
observing = true;
}

func stopObservingHeight() {
webview.scrollView.removeObserver(self, forKeyPath: "contentSize", context: &MyObservationContext)
observing = false
}

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard let keyPath = keyPath else {
super.observeValueForKeyPath(nil, ofObject: object, change: change, context: context)
return
}
switch (keyPath, context) {
case("contentSize", &MyObservationContext):
webviewHeightConstraint.constant = webview.scrollView.contentSize.height
default:
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
}

extension ViewController: UIWebViewDelegate {
func webViewDidFinishLoad(webView: UIWebView) {
print(webView.request?.URL)
webviewHeightConstraint.constant = webview.scrollView.contentSize.height
if (!observing) {
startObservingHeight()
}
}
}

UIWebView with dynamic height inside a UIScrollView in Objective-C

add a height constraint to your webView and make a IBOutlet of that like

@property(strong, nonatomic) IBOutlet NSLayoutConstraint *webViewHeightConstraint;

load your webview and in web view delegate

- (void)webViewDidFinishLoad:(UIWebView *)webView

get webview content height and set webViewHeightConstraint.constant
like below:-

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

NSString *str = [webView stringByEvaluatingJavaScriptFromString:@"(document.height !== undefined) ? document.height : document.body.offsetHeight;"];
CGFloat height = str.floatValue;
webViewHeightConstraint.constant = height;

}

hope it may help you.

UITableViewCell With UIWebView Dynamic Height

TableView will resize cells itself, you just need implement tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat delegate method.

Yes, you don't know the height of WebView initially, but you can calculate it and then ask TableView to reload cell. Something like this:

class TableViewController: UITableViewController, UIWebViewDelegate
{
var content : [String] = ["test1<br>test1<br>test1<br>test1<br>test1<br>test1", "test22<br>test22<br>test22<br>test22<br>test22<br>test22"]
var contentHeights : [CGFloat] = [0.0, 0.0]

// ...

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("newsCell", forIndexPath: indexPath) as! NewsTableViewCell
let htmlString = content[indexPath.row]
let htmlHeight = contentHeights[indexPath.row]

cell.webView.tag = indexPath.row
cell.webView.delegate = self
cell.webView.loadHTMLString(htmlString, baseURL: nil)
cell.webView.frame = CGRectMake(0, 0, cell.frame.size.width, htmlHeight)

return cell
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return contentHeights[indexPath.row]
}

func webViewDidFinishLoad(webView: UIWebView)
{
if (contentHeights[webView.tag] != 0.0)
{
// we already know height, no need to reload cell
return
}

contentHeights[webView.tag] = webView.scrollView.contentSize.height
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: webView.tag, inSection: 0)], withRowAnimation: .Automatic)
}

// ...
}


Related Topics



Leave a reply



Submit