產品中頻繁遇到UITableViewCell含有WebView的需求,也提出好幾個解決方案了,當然一次比一次簡單。
舊方案
去年我總結出這個方案完美解決 UITableViewCell 中載入 UIWebView 的高度計算問題,我的思路是:
- 獲取資料,確定 tableView 的 cell 的數目和初始高度。
- 重新整理 tableView,向每一個 cell 填充內容和初始高度,將初始高度賦值給 cell 的一個變數 cellHeight,並載入 webView。
- 等第 i 個 cell 中的 webView 載入完畢之後計算其高度 h。
- 令 h 與 cellHeight 進行比較。如果兩個高度不等,通知 tableView 第 i 個 cell 的高度 h (即 cell 中 webView的實際高度),並呼叫如下程式碼重新整理 cell:
tableView.reloadRows(at: [IndexPath (row: i, section: 0)], with: .none)
複製程式碼
如果相等,OK,第 i 個 cell 顯示正確。
- 重複 3。
新方案
最近在做一個新的產品,又遇到了這個需求。本來我的想法是從上一個專案直接copy程式碼過來,但是看了半天覺得太過繁瑣,再加上最近看了一些UITableViewCell自適應高度的文章,就想換種寫法。
一般情況下,實現UITableViewCell自適應高度這樣做:
- 設定UITableView自適應cell高度
tableView.estimatedRowHeight = 76
tableView.rowHeight = UITableView.automaticDimension
複製程式碼
- 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
}
複製程式碼
這樣一來就很清爽了。