兩個協議的細節: UITableViewDelegate, UITableViewDataSource, and heightForRowAtIndexPath
在 iOS 開發中,UITableView 無疑是最常使用的控件之一。其幾乎被用於所有的 iOS 應用中,API 設計之經典,足以滿足大多應用場景並完成許多繁重的任務。作爲蘋果工具箱中少有的具有兩個協議的(UITableViewDelegate 和 UITableViewDataSource)API ,其不僅優秀,同樣可以給我們一些有趣的啓發。
簡單來看,數據源和代理的區別是明顯且受歡迎的,數據源提供實際的數據,同時代理負責提供可視化佈局以及如何處理用戶交互等相關信息。我們都是機智的,深知將內容和佈局顯示分開是件好事情,並且我們習慣於蘋果設計的 API ,同時鼓勵同其設計行爲保持一致。
但是,令人疑惑的是,有一個方法似乎並不符合這種細節:tableView:heightForRowAtIndexPath:
。在許多情況下,表控件的單個 cell 的高度要取決於其中所展示的數據,這是避無可避的。如果一段數據要消失了,是不是要縮小 cell 使其不再顯示?或者可能需要延長 cell 使其適應更長的文本。你需要使用你的模型數據來確定如何計算,如此,作爲 UITableViewDelegate
中的方法,必然要混雜相關數據,那麼不就同蘋果鼓勵的模式相違背了麼!
當然,實際上開發人員習慣將代理和數據源設置爲一個對象,所以這似乎更像一個語義分歧,而不是其他什麼。但這並不能解釋 tableView:heightForRowAtIndexPath:
作爲一個代理中的方法,卻需要時常訪問底層數據。
這是我的理論。我們都熟悉 tableView:cellForRowAtIndexPath:
方法,其作爲 UITableViewDataSource
中的方法,負責根據索引返回有效的 UITableViewCell
實例對象。大多和我交流過的開發人員都會在這個方法中進行需要的配置以及佈局,或者是不能放在子類中的一些配置及佈局。但是,如果不在該方法中進行,又該在何處呢?
UITableViewDelegate
中有一個鮮爲人知的方法 tableView:willDisplayCell:forRowAtIndexPath:
。在 tableView:cellForRowAtIndexPath:
調用後,系統自己會進行一些佈局配置,然後在進行實際的屏幕渲染前,調用這個鮮爲人知的方法。如果這就是蘋果想要我們對 cell 進行相關配置的地方呢?
自從 7、8 個月前,我的同事向我介紹了這個方法後,我便很高興的開始這樣做。就像 tableView:heightForRowAtIndexPath:
方法只負責收集相關區域內對象的實現的實現細節計算出一個有效的高度值一樣,我也趨向於使用 tableView:willDisplayCell:forRowAtIndexPath:
來進行視圖的一些配置,它們因爲各種原因無法放在子類中。如此,tableView:cellForRowAtIndexPath:
的存在就只是聲明並初始化一個正確的 UITableViewCell
類型的實例對象,然後該對象會傳遞給 tableView:willDisplayCell:forRowAtIndexPath:
方法,去進行其他繁瑣的配置。
這並不會妨礙我創建表示對象,或者使用其他開發人員多年來提出的好用的設計模式,但是這種分離確實值得注意:一個方法只負責實例化對象,另一個方法負責配置該對象。
也許這只是一個學術練習,也許其中有一些我沒有意識到的明顯的邏輯問題(如果有,你完全可以在推特上呼喊我)。但是在實際的應用中,我發現我的表視圖代碼更加整潔,也更加容易理解了,自從我將實例化和配置分別放在 tableView:cellForRowAtIndexPath:
和 tableView:willDisplayCell:forRowAtIndexPath:
方法中後。而這也使得 tableView:heightForRowAtIndexPath:
方法作爲 UITableViewDelegate
而不是 UITableViewDataSource
中的方法成了一個明智的決定。