You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于WKWebView内容高度实时更新UITableViewCell高度?

问题

需要实现基于WKWebView内容高度动态更新UITableViewCell的高度。最初尝试用document.body.offsetHeight获取高度,但WebView末尾会出现空白,于是改用监听WKWebView导航完成事件,通过webView.scrollView.contentSize.height设置约束并刷新TableView:

extension PDDescriptionCell: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        self.summaryHeight?.constant = 0
        self.contentView.layoutIfNeeded()
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        self.summaryHeight?.constant = webView.scrollView.contentSize.height
        if let tableView = self.superview as? UITableView {
            tableView.beginUpdates()
            tableView.endUpdates()
        }
        self.contentView.layoutIfNeeded()
    }
}

但实际运行时,位于ProductDetail UITableViewController第3行的PDDescriptionCell,首次滚动到该行时高度还是xib的初始值,只有滚动回第1行再返回第3行,高度才会更新为正确值。希望沿用当前获取高度的逻辑,解决更新时机的问题。

PDDescriptionCell完整代码如下:

class PDDescriptionCell: UITableViewCell {
    var product: ProductModel? {
        didSet {
            if let product = product {
                print(product.summary)
                loadContent(product.summary)
            }
        }
    }

    IBOutlet weak var summaryView: WKWebView!
    IBOutlet weak var summaryHeight: NSLayoutConstraint!

    private let configuration = WKWebViewConfiguration()

    override func awakeFromNib() {
        super.awakeFromNib()

        print("awake from nib")

        contentView.backgroundColor = .background1

        summaryView.navigationDelegate = self

        let preference = WKWebpagePreferences()
        preference.preferredContentMode = .mobile
        preference.allowsContentJavaScript = true

        let configuration = WKWebViewConfiguration()
        configuration.defaultWebpagePreferences = preference

        if let product = product {
            _ = WKWebViewConfiguration()
            print("HTML string is loaded")
            self.summaryView.loadHTMLString(product.summary, baseURL: nil)
            self.loadContent(product.summary)
        }
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

    private func loadContent(_ content: String) {
        let styledHTML = """
    <html>
    <head>
    <style> body { font-size: 30px; /* Change the font size for the body as needed */ font-family: -apple-system; /* Use the system font */ } table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #dddddd; text-align: left; padding: 8px; font-size: 20px; /* Change the font size for table cells as needed */ } th { background-color: #f2f2f2; } img { width: 100%; /* Set the width of images to 100% */ height: auto; /* Set the height of images to auto to maintain aspect ratio */ margin-bottom: 50px; /* Add space below each image */ } .semibold { font-weight: 600; /* Set font weight to semibold */ } </style>
    <script type="text/javascript"> window.onload = function() { var body = document.body; var html = document.documentElement; var height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); window.webkit.messageHandlers.iOS.postMessage(height); } </script>
    </head>
    <body>
    \(content)
    </body>
    </html>
    """
        self.summaryView.loadHTMLString(styledHTML, baseURL: nil)
    }
}

extension PDDescriptionCell: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        self.summaryHeight?.constant = 0
        self.contentView.layoutIfNeeded()
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        self.summaryHeight?.constant = webView.scrollView.contentSize.height
        if let tableView = self.superview as? UITableView {
            tableView.beginUpdates()
            tableView.endUpdates()
        }
        self.contentView.layoutIfNeeded()
    }
}

也曾尝试用evaluateJavaScript("document.readyState")和evaluateJavaScript("document.body.scrollHeight")获取高度,但仍存在末尾空白问题,希望保留当前高度获取逻辑。

解决方案

1. 修正awakeFromNib中的无效代码

原awakeFromNib存在两处无效逻辑:重新创建的WKWebViewConfiguration未被使用,且product在此时大概率未赋值,加载HTML的代码不会执行。修改后:

override func awakeFromNib() {
    super.awakeFromNib()

    print("awake from nib")
    contentView.backgroundColor = .background1
    summaryView.navigationDelegate = self
    
    // 禁用WebView自身滚动,避免与TableView滚动冲突
    summaryView.scrollView.isScrollEnabled = false
}

2. 正确获取所在的UITableView

当前通过self.superview as? UITableView获取TableView的方式错误(Cell的直接父视图是内容容器,而非TableView),添加递归查找方法:

private func findTableView() -> UITableView? {
    var view = superview
    while view != nil {
        if let tableView = view as? UITableView {
            return tableView
        }
        view = view?.superview
    }
    return nil
}

替换didFinish中的TableView获取逻辑,改用精准刷新当前行:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    self.summaryHeight?.constant = webView.scrollView.contentSize.height
    
    if let tableView = findTableView(), let indexPath = tableView.indexPath(for: self) {
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }
    
    self.contentView.layoutIfNeeded()
}

3. 确保TableView自动高度配置正确

在ProductDetail UITableViewController的viewDidLoad中添加自动行高配置:

override func viewDidLoad() {
    super.viewDidLoad()
    // 启用自动计算行高
    tableView.rowHeight = UITableView.automaticDimension
    // 设置预估行高(尽量接近实际高度,减少滚动跳动)
    tableView.estimatedRowHeight = 400 // 根据xib初始值调整
}

4. 延迟获取高度(解决图片加载导致的偏差)

若HTML包含图片,didFinish触发时图片可能未加载完成,导致高度计算不准确,可添加短延迟:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
        guard let self = self else { return }
        self.summaryHeight?.constant = webView.scrollView.contentSize.height
        
        if let tableView = self.findTableView(), let indexPath = tableView.indexPath(for: self) {
            tableView.reloadRows(at: [indexPath], with: .automatic)
        }
        
        self.contentView.layoutIfNeeded()
    }
}

5. 清理冗余脚本

HTML中window.webkit.messageHandlers.iOS.postMessage(height)的脚本未被监听,属于冗余代码,可直接删除。


内容的提问来源于stack exchange,提问作者Shahnas Ettool

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 09:52:03