使用自动布局使UIWebView高度与其内容相同

17

我在一个滚动视图中嵌入了一个 Web 视图,希望将 Web 视图“展开”以显示其所有内容,并希望使用滚动视图滚动查看。

我使用滚动视图作为父视图而不是仅使用 Web 视图作为滚动视图的原因是,我在 Web 视图上方有标签,我希望在滚动过程中包括这些标签(请参见截图)。

那么,如何使 Web 视图调整大小以占用其需要显示其包含内容的所有空间?

之后,如何使父视图(滚动视图)根据 Web 视图的大小调整自身大小?

1

2

2个回答

31
为了使这个工作正常,您需要按照以下步骤进行操作:
  1. nib 中的 UIWebView 连接到视图控制器中的一个插座(outlet)。
  2. 禁用 Web 视图中的滚动。
  3. UIScrollView、在 Web 视图上方的 UIView (在我的示例中,我省略了该视图中的所有标签)以及 UIWebView 设置约束。
  4. 将 UIWebView 的 height 约束连接到视图控制器中的插座(outlet)。
  5. 将视图控制器设为 UIWebViewDelegate
  6. webViewDidFinishLoad 中,将高度约束的常量设置为 Web 视图内滚动视图的 contentSize 的高度。
  7. 开始 Key-Value Observing 监听 contentSize 以更改高度,当 Web 页面的某些部分因未重新加载页面而改变其大小时(例如手风琴或菜单)就需要更改 Web 视图的高度。

我不会详细解释约束,因为您似乎已经自己弄清楚了。以下是约束的截图:

enter image description here

所以,以下是代码:

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

嗨joern,好答案。我很好奇为什么在这个例子中MyObservationContext是全局的。它似乎作为类级变量也能正常工作。你能给我解释一下吗?谢谢。+1 - Dan Beaulieu
我观察到这极其不准确。当每次调用webViewDidFinishLoad时更新约束,最终只能显示屏幕上能容纳的部分。如果只在最后一次调用(webView.isLoading == false)时更新约束,则可以获取几乎整个页面,但末尾会被截断。 - Julian F. Weinert
@JulianF.Weinert,您能否发布一个出现此问题的URL?我测试过的URL都可以正常工作。 - joern
很遗憾,不是这样的。它是由API接收到的HTML。我在KVO部分有点出了问题。虽然我已经让它运行起来了,但是底部偶尔会被切掉。有趣的是,contentSize是错误的。如果我要猜的话,我会说KVO正在丢失一些事件,但从苹果值得信赖的角度来看,我非常怀疑这一点。 - Julian F. Weinert
还是不太明白。我重写了一些代码,然后意识到如果所有内容都在缓存中,我根本不会收到webViewDidFinishLoad的调用,或者它太早了,我不确定。但是当我每次加载HTML时添加观察者,一切都正常!肯定有些愚蠢的地方我没看到。干得好,非常感谢! - Julian F. Weinert
显示剩余4条评论

4

来自@joern的绝妙解决方案。对我很有效。我只是将他的代码改成了Swift 3。

Swift 3更新代码:

我还修改了stopObservingHeight以防止在创建之前删除观察者。

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

case("contentSize", &MyObservationContext): 中加入 let context = contextguard 语句中将解决错误。非常好的更新! - John Pang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接