1. 程式人生 > >UITableViewCell含有WebView的自適應高度新解決方案

UITableViewCell含有WebView的自適應高度新解決方案

產品中頻繁遇到UITableViewCell含有WebView的需求,也提出好幾個解決方案了,當然一次比一次簡單。

舊方案

去年我總結出這個方案完美解決 UITableViewCell 中載入 UIWebView 的高度計算問題,我的思路是:

  1. 獲取資料,確定 tableView 的 cell 的數目和初始高度。
  2. 重新整理 tableView,向每一個 cell 填充內容和初始高度,將初始高度賦值給 cell 的一個變數 cellHeight,並載入 webView。
  3. 等第 i 個 cell 中的 webView 載入完畢之後計算其高度 h。
  4. 令 h 與 cellHeight 進行比較。如果兩個高度不等,通知 tableView 第 i 個 cell 的高度 h (即 cell 中 webView的實際高度),並呼叫如下程式碼重新整理 cell:
tableView.reloadRows(at: [IndexPath (row: i, section: 0)], with: .none)
複製程式碼

如果相等,OK,第 i 個 cell 顯示正確。

  1. 重複 3。

新方案

最近在做一個新的產品,又遇到了這個需求。本來我的想法是從上一個專案直接copy程式碼過來,但是看了半天覺得太過繁瑣,再加上最近看了一些UITableViewCell自適應高度的文章,就想換種寫法。

一般情況下,實現UITableViewCell自適應高度這樣做:

  1. 設定UITableView自適應cell高度
    tableView.estimatedRowHeight = 76
    tableView.rowHeight = UITableView.automaticDimension
複製程式碼
  1. UITableViewCell設定從top到bottom完整的約束
    questionWebView.snp.makeConstraints { (make) in
        make.edges.equalToSuperview().inset(UIEdgeInsets.init(top: 8, left: 16, bottom: 8, right: 16))
        make.height.equalTo(60)
    }
複製程式碼

對於一般的view,這兩步之後就可以實現cell的自適應高度了。

但是對於webView,在開始載入時webView的高度並不固定,所以要在webView載入完畢後獲取其高度並重新整理cell。這一步就不用舊方案的step4來重新整理cell了。

首先為webView監聽webView載入:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        tableView.separatorStyle = .none
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ExerciseQuestionCell
        // 為cell設定資料
        cell.setData(exerciseArray[indexPath.row])
        // 監聽webView載入
        cell.questionWebView.delegate = self
        cell.selectionStyle = .none
        return cell
    }
複製程式碼

獲取webView的高度。

   func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // 當webview載入完成,計算並重新整理高度
        webView.evaluateJavaScript("document.body.scrollHeight") { (any, error) in
            // height就是載入完畢的webView的高度
            let height = any as! Int
            
        }
    }
複製程式碼

獲取到高度後調整webView的高度:

   func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // 當webview載入完成,計算並重新整理高度
        webView.evaluateJavaScript("document.body.scrollHeight") { (any, error) in
            // height就是載入完畢的webView的高度
            let height = any as! Int
            
            // 調整webView高度
            loadingWebView.snp.updateConstraints { (make) in
                make.height.equalTo(height)
            }
        
            // 重新整理tableView
            exerciseTable.beginUpdates()
            exerciseTable.endUpdates()
        }
    }
複製程式碼

到這裡效果就出來了

但是在這裡我發現當滑動table時模擬器會變得十分卡頓,除錯發現ExerciseQuestionCell的setData(exercise)方法在一直被呼叫,setData方法如下:

    func setData(_ exercise: Exercise) {
        do {
            let data = exercise.question.data(using: .utf8)
            let questionObject = try JSON.init(data: data!)
            let question = questionObject["question"].stringValue
            let questionHtml = DIV_HEAD + question + DIV_FOOT
            webView.loadHTMLString(htmlString, baseURL: nil)
        } catch {
            print(error)
        }
    }
複製程式碼

我想可能是重新整理tableView的時候呼叫setData方法,setData裡面呼叫webView.loadHTMLString方法載入html,載入完畢後又重新整理tableView......產生迴圈,所以我在ExerciseQuestionCell裡面設定了一個變數loadedData來記錄cell是否設定過資料,如果設定過資料就不再設定了:

    func setData(_ exercise: Exercise) {
        if loadedData {
            // 設定過資料之後就不再設定,防止多次重新整理
            return
        }
        do {
            let data = exercise.question.data(using: .utf8)
            let questionObject = try JSON.init(data: data!)
            let question = questionObject["question"].stringValue
            let questionHtml = DIV_HEAD + question + DIV_FOOT
            questionWebView.loadHtmlString(questionHtml)
        } catch {
            print(error)
        }
        loadedData = true
    }
複製程式碼

這樣一來就很清爽了。