Make a UIWebView as height as its content with Auto Layout

14,152

Solution 1

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:

enter image description here

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

Solution 2

Great solution from @joern. Works fine for me. I just adapted his code to swift 3.

Swift 3 Update Code :

I also modify stopObservingHeight to prevent remove observer before it was create.

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.isScrollEnabled = 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() {
        if observing {
            webView.scrollView.removeObserver(self, forKeyPath: "contentSize", context: &MyObservationContext)            
            observing = false
        }
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard let keyPath = keyPath,
            let context = context else {
            super.observeValue(forKeyPath: nil, of: object, change: change, context: nil)
            return
        }
        switch (keyPath, context) {
        case("contentSize", &MyObservationContext):
            webviewHeightConstraint.constant = webview.scrollView.contentSize.height
        default:
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}

extension ViewController: UIWebViewDelegate {
    func webViewDidFinishLoad(_ webView: UIWebView) {
        print(webView.request?.url ?? "nil")
        webviewHeightConstraint.constant = webview.scrollView.contentSize.height
        if (!observing) {
            startObservingHeight()
        }
    }
}
Share:
14,152

Related videos on Youtube

Jack Berstrem
Author by

Jack Berstrem

Updated on September 15, 2022

Comments

  • Jack Berstrem
    Jack Berstrem over 1 year

    I've got a web view embedded in a scroll view, and I want the web view to be "expanded" to show all of its content, which I then want to scroll through with my scroll view.

    The reason I am using a scroll View as a super view and not just using the web view as a scroll view is because I have labels above the web view that I want to include in the scrolling process (see screenshot).

    So how do I make the webview resize itself so it takes up all the space it needs to show the content it contains ?

    And after that, how do I make the superview (the Scroll view) resize itself according to the size of the webview?

    1

    2

  • Jack Berstrem
    Jack Berstrem over 8 years
    Hey! I saw your answer when you initially posted it but decided to fix the problem when I would be working on the second version of my app. Thanks a lot, I just tried to fix everything and it worked very well. I just had to fix a few constraint values to offset the scrollView vertically (since I also have a navigation bar at the top, it's not exactly an empty superview). This is the first time I use code with observers, so I'll have to look at it more closely later, but for now, it works! Thanks
  • joern
    joern over 8 years
    Thank you for your feedback, I'm glad it helped you.
  • Dan Beaulieu
    Dan Beaulieu almost 8 years
    Hi joern, good answer. I'm curious why the MyObservationContext is global in this example. It seems to work just fine as a class level variable as well. Could you shine some light on this for me? Thanks. +1
  • joern
    joern almost 8 years
    Hi Dan. You're right, it should be a class property. No reason to make it global. Thanks for your comment!
  • Julian F. Weinert
    Julian F. Weinert almost 8 years
    I observed that this is extremely inaccurate. When I update the constraint each time webViewDidFinishLoad gets called, I end up with not more than what fits on screen. If I do it only on the final call (webView.isLoading == false), I get almost the full page, but with the end cut off.
  • joern
    joern almost 8 years
    @JulianF.Weinert Can you post a URL where this happens? It works fine with the URLs that I tested.
  • Julian F. Weinert
    Julian F. Weinert almost 8 years
    Unfortunately not. It's HTML received by an API. I kinda screwed the KVO part. Since I got it running I ALMOST got rid of the problem. However, occasionally the bottom gets cut. The fun part is that the contentSize is wrong. If I'd guess I would say KVO is dropping some events, but from an apple-trusting perspective I highly doubt.
  • Julian F. Weinert
    Julian F. Weinert almost 8 years
    Still not getting it. I rewrote some of the code and then realised that if everything is in the cache, I won't get the webViewDidFinishLoad call at all or it's WAY too early, I'm not fully sure. But when I add the observer whenever I load HTML everything works! There must have been something stupid I didn't see. Gr8 work, thanks a lot!
  • John Pang
    John Pang about 6 years
    add let context = context into guard will resolve the error in case("contentSize", &MyObservationContext):. Great update!
  • Dilip Tiwari
    Dilip Tiwari about 6 years
    i followed ur code as per instruction it is webView size it not increasing when data is more could u help me with this issue @joern