iOS Swift UITableView的scrollToRow的”坑”

LinXunFeng發表於2019-02-22

簡介

在tableView中,我們一般會用到scrollToRow這個來控制tableView滾到指定的某一行。一般寫法如下所示

// MARK: 滾到底部
func scrollToBottom(animated: Bool = false) {
    if dataArr.count > 0 {
        tableView.scrollToRow(at: IndexPath(row: dataArr.count - 1, section: 0), at: .bottom, animated: animated)
    }
}
複製程式碼

情況

今天鄙人使用SnapKit來佈局cell,然後用scrollToRow來滾到底部就遇到了一個很奇葩的現象。
我設定了在鍵盤彈出後聊天訊息列表會自動滾到底部。
1.隨便輸入一條訊息,點傳送後,在聊天訊息列表中並沒有滾到最新訊息那一行。
2.退出鍵盤不做任何操作再開啟鍵盤也是滾到剛才那裡(即最新訊息的上一條所在位置)
3.只有在退出鍵盤後把聊天訊息列表的訊息向上拉一點距離露出最新訊息所在的cell之後,再點選才有用

iOS   Swift UITableView的scrollToRow的”坑”

分析

在無奈之下,經過了一步步的探索,終於發現了問題的所在
首先我們要了解一下scrollToRow執行後會呼叫哪些函式及順序
會呼叫這兩個方法

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
複製程式碼
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
複製程式碼

步驟一

我在 heightForRow 中寫了具體的資料,也就是把高度寫死,不再是動態獲取。接著執行程式得到如下結果
比如我原本有10條資料,現在加入了一條後執行了scrollToRow,它會
1.先呼叫 heightForRow 11次,即包括最新加入的那一條
2.然後再呼叫 cellForRow
3.最後在調一次 heightForRow
後面的2和3是針對最新訊息的

步驟二

我在 heightForRow 中不再寫死高度,而是從模型資料中動態獲取高度(高度是在cell佈局後獲取的,再賦值到模型資料中的cellHeight變數)

執行程式得到這個結果:呼叫 heightForRow 11次,然後就沒了
複製程式碼

好吧,問題就出現在對heightForRow的第11次呼叫,前10次都有返回具體的高度,而最後一次是0~。

結論

現在清楚了,要想在呼叫 scrollToRow 到指定的那一行,前提條件是那一行的高度不能為0。
所以在上面的情況中,傳送完訊息後,最新訊息的cell的確是插入到了tableView,也有顯示出來(後面我自己測的),但就是無法滾到最新訊息那一行,就是因為 heightForRow 返回的高度為0
在上面的情況中,向上拉一點距離露出cell後scrollToRow才有效就是因為此時heightForRow返回的高度不再為0

解決方案

按本人自身的情況來說,有兩種解決方法

第一種

在傳入的模型資料中給予明確計算出來的數值就好。

第二種

我使用SnapKit來自動佈局cell的位置然後再來獲取高度,這做法主要就是為了避免運算。所以我不選用第一種解決方法
好了,方法如下:

// dataArr是用來存放模型的陣列
let indexPath = IndexPath(row: dataArr.count - 1, section: 0)
// 呼叫tableView的資料來源辦法
_ = self.tableView(tableView, cellForRowAt: indexPath)
複製程式碼

在插入最新訊息後,呼叫tableView的資料來源方法來讓它先對cell進行佈局,這樣就獲取到了cell的高度,然後再執行 scrollToRow 就好了。

完美

相關文章