PIXEL
DOCK

I like the smell of Swift in the morning…

Set a UIWebView’s height to the height of its HTML content using Auto Layout

Posted: | Author: | Filed under: iOS, Swift | Tags: , | 11 Comments »

In a previous post I described how to programmatically set the height of a UIWebView to fit the height of its HTML content.

This is a different approach using a Storyboard (and a little code). To make things a little bit more interesting I added an UIView that sits on top of the UIWebView and that should scroll out of view when the user scrolls the UIWebView. To make that happen we need an UIScrollView that contains the UIView and the UIWebView:

The UIView could be something like an iAd that you want to display on top of the web content but that should be scrolled out of the view when the user scrolls the web content.

Here is how to make it work:

  1. Connect the UIWebView from the nib to an outlet in your view controller.
  2. Disable scrolling in the UIWebView.
  3. Set the constraints on the `UIScrollView`, the `UIView` and the `UIWebView`:
    • The UIScrollView needs a top, a bottom, a leading and a trailing constraint to the UIViewController’s view.
    • The UIView needs a top, a leading and a trailing constraint to the UIScrollView. It also needs a width constraint that is equal to the UIScrollView’s width to avoid horizontal scrolling (See this post for an explaination). I also add a height constraint, because I want to have the UIView to have a constant height of 100pt.
    • The UIWebView needs a top constraint to the UIView’s bottom, a leading, a trailing and a bottom constraint to the UIScrollview. It also needs a height constraint that we will later set to the height of the HTML content

    The constraints should look like this:

    0FJCG

  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). Don’t forget to stop observing when the view controller gets deallocated.

Here is the code that you need:

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
        if let url = URL(string: "https://www.google.de/intl/de/policies/terms/regional.html") {
            webview.loadRequest(URLRequest(url: url))
        }
    }
    
    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 observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard let keyPath = keyPath else {
            super.observeValue(forKeyPath: nil, of: object, change: change, context: context)
            return
        }
        switch keyPath {
        case "contentSize":
            if context == &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) {
        webviewHeightConstraint.constant = webview.scrollView.contentSize.height
        if (!observing) {
            startObservingHeight()
        }
    }
}

In case you have difficulties implementing this, i put a working example on github

EDIT

GitHub user kenji21 implemented this cool UIWebView subclass that get’s the job done with less code:

class ContentFittingWebView: UIWebView {
    
    var contentSizeObservationToken: NSKeyValueObservation?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        startObservingHeight()
    }

    override var intrinsicContentSize: CGSize {
        return scrollView.contentSize
    }
    
    func startObservingHeight() {
        contentSizeObservationToken?.invalidate()
        contentSizeObservationToken = scrollView.observe(\UIScrollView.contentSize, options: [.new], changeHandler: { (scrollView, change) in
            self.invalidateIntrinsicContentSize()
        })
    }
}

11 Comments

  • 1

    Goshisaid at

    Thank you so much for this. It works so great and it’s so simple.

  • 2

    Akshaysaid at

    I’m having problem with setting the constraints just as you have done above. If possible could you please upload a sample project with the constraints. I’m getting – Ambiguous scrolling content error in the storyboard

  • 3

    Jörnsaid at

    Hi Akshay,

    thanks for your comment. I put a working example on github. You find the link at the bottom of the article. Hope it helps!

  • 4

    meladsaid at

    Thanks for the great support

  • 5

    Jessesaid at

    How would you go about doing this, but instead of using a url passing json data through an api?

  • 6

    Jörnsaid at

    Thanks for your comment, Jesse! I’m afraid I don’t fully understand what you are asking. Could you provide more details about what you are trying to do?

  • 7

    Mayursaid at

    Hi Jorn
    I have a very critical problem while using this.
    When i zoom uiwebview using pinch app gets crash at line : webviewHeightConstraint.constant = webview.scrollView.contentSize.height

    It gives me _NSLayoutConstraintNumberExceedsLimit exception. As i have checked webView’s scrollView content size it gets dramatically increased. to number in millions so it may be exceeds the internal limit of webview.

    I need zoom in and out functionality in addition to your solution can you help me..

  • 8

    Jörnsaid at

    Hi Mayur,

    thanks for your comment. I tried zooming the web view and ran into the same issue. I have not found a workaround yet. When I have a bit more time I’ll try to find a work around but I cannot promise anything 😉

  • 9

    jessesaid at

    Did you ever find a workaround for the zoom issue? It glitches the crap out of my app when zooming.

  • 10

    Jörnsaid at

    No, unfortunately not.

  • 11

    Subbusaid at

    Thanks for the support. it works so great and its simple


Leave a Reply