我們一般創(chuàng)建 UITableViewCell 會(huì)這么寫(xiě):
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
}
return cell!
}
如果需要注冊(cè),則這么寫(xiě):
// 注冊(cè) cell
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
return cell
}
有區(qū)別嗎?一個(gè)最明顯的區(qū)別就是,一個(gè)是可選的 ( Optional ),一個(gè)是一定有值的;還有就是,使用第二種方法時(shí),如果事先未注冊(cè) Cell,則會(huì)引發(fā)崩潰,目的是為了提醒開(kāi)發(fā)者:你忘記注冊(cè)了!
基于以上兩點(diǎn),官方建議使用第二種方法創(chuàng)建 UITableViewCell 。
擴(kuò)展 UITableView
為了解決每次創(chuàng)建 Cell 時(shí)都要手動(dòng)的給它注冊(cè)一遍的問(wèn)題,需要給它擴(kuò)展一個(gè)方法,怎么做呢?
我們的目的是不用手法注冊(cè),也就意味著第二種方法已經(jīng)行不通(需要注冊(cè)),那我們把目光聚焦在第一個(gè)方法上。
怎么做呢?解決重復(fù)的代碼是第一步,假如每次創(chuàng)建 Cell 都要寫(xiě)這么一個(gè)判斷,那不是煩死了?為了能夠重用,我們可以封裝一下,比如:
func createCell(tableView: UITableView, style: UITableViewCellStyle, reuseIdentifier: String) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier)
if cell == nil {
cell = UITableViewCell(style: style, reuseIdentifier: reuseIdentifier)
}
return cell!
}
然后我們使用這個(gè)自定義創(chuàng)建 Cell 的方法:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = create(tableView: tableView, style: .default, reuseIdentifier: "Cell")
return cell
}
然則如果把方法封裝在 tableView 的 extensions 里面,則更好一些,因?yàn)樵摲椒ㄊ腔?tableView 來(lái)調(diào)用的,可以這樣:
public extension UITableView {
func dequeueReusableCell(style: UITableViewCell.CellStyle = .default, identifier: String? = nil) -> UITableViewCell {
var cell = dequeueReusableCell(withIdentifier: identifier)
if cell == nil {
cell = UITableViewCell(style: style, reuseIdentifier: identifier)
}
return cell!
}
}
來(lái)試一下:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(style: .default, identifier: "Cell")
return cell
}
這樣一來(lái)感覺(jué)就好很多了,看起來(lái)和調(diào)用系統(tǒng)的方法差不多。但是還有兩個(gè)問(wèn)題:
- 創(chuàng)建不同類型的 Cell
在這里我們使用的是UITableViewCell類型創(chuàng)建 Cell,如果需要?jiǎng)?chuàng)建不同的類型怎么辦? - 創(chuàng)建來(lái)自 Nib 的 Cell
在這里我們只使用代碼的方式創(chuàng)建 Cell,如果要?jiǎng)?chuàng)建來(lái)自 Nib 的 Cell 怎么辦?
我們來(lái)逐一解決這兩個(gè)問(wèn)題。
創(chuàng)建不同類型的 Cell
解決第一個(gè)問(wèn)題的一個(gè)有效的方法就是使用泛型,我們?cè)诜椒ɡ锩嫘略鲆粋€(gè)表示 UITableViewCell 的泛型類。如下所示:
func dequeueReusableCell<T: UITableViewCell>(cellType: T.Type = T.self, style: UITableViewCell.CellStyle = .default, identifier: String? = nil) -> T {
var cell = dequeueReusableCell(withIdentifier: identifier ?? "\(cellType)") as? T
if cell == nil {
cell = T(style: style, reuseIdentifier: identifier ?? "\ cellType)")
}
return cell!
}
來(lái)分析一下這個(gè)方法,首先方法添加了一個(gè)泛型 <T: UITableViewCell> ,目的是為了能夠創(chuàng)建不同類型的 Cell 并返回(注意,返回值也是 T )。
然后接下來(lái)的一行代碼是:
// 使用重用標(biāo)識(shí)符從緩存池中獲取 Cell
var cell = dequeueReusableCell(withIdentifier: identifier ?? "\(cellType)") as? T
該方法的意思注釋已經(jīng)說(shuō)了:是使用重用標(biāo)識(shí)符從緩存池中獲取 Cell。那么這個(gè)重用標(biāo)識(shí)符應(yīng)該傳什么呢?你當(dāng)然可以傳方法參數(shù)里面的 identifier ,但一個(gè)更好的方式是使用 Cell 的類名來(lái)作為它的重用標(biāo)識(shí)符。綜上所述,在沒(méi)有指定 identifier 參數(shù)的情況下使用 Cell 的類名來(lái)作為它重用標(biāo)識(shí)符,它應(yīng)該傳:identifier ?? "\(cellType)" 。
接下來(lái)的一段代碼是:
if cell == nil {
cell = T(style: style, reuseIdentifier: identifier ?? "\(cellType)")
}
在判斷 cell 為空時(shí)執(zhí)行,和普通的創(chuàng)建方式一樣,只是這里使用了 T 代替 UITableViewCell ,泛型 T 指定了它應(yīng)該是 UITableViewCell 類,所以本質(zhì)上它跟 UITableViewCell 類是一樣的。
這樣一來(lái),在不使用 Nib 的情況下,這個(gè)方法完全沒(méi)有問(wèn)題了。來(lái)試一下:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 不指定類型,默認(rèn)返回 `UITableViewCell`。
// let cell = tableView.dequeueReusableCell()
// 返回 MyTableViewCell
// let cell = tableView.dequeueReusableCell() as MyTableViewCell
// 返回 MyTableViewCell
// let cell: MyTableViewCell = tableView.dequeueReusableCell()
// 返回 MyTableViewCell
let cell = tableView.dequeueReusableCell(cellType: MyTableViewCell.self)
return cell
}
創(chuàng)建來(lái)自 Nib 的 Cell
為了能夠區(qū)別于代碼創(chuàng)建的方式,我們?cè)诙x一個(gè)專門用來(lái)處理創(chuàng)建 Nib Cell 的方法,因?yàn)槿绻挤旁谝黄?,邏輯處理起?lái)會(huì)比較復(fù)雜。
func dequeueReusableNibCell<T: UITableViewCell>(cellType: T.Type = T.self, nibName: String? = nil) -> T {
var cell = dequeueReusableCell(withIdentifier: nibName ?? "\(cellType)") as? T
if cell == nil {
cell = UINib(nibName: nibName ?? "\(cellType)", bundle: nil).instantiate(withOwner: nil).last as? T
}
return cell!
}
其實(shí)情況差不多,標(biāo)識(shí)符參數(shù)由 identifier 變成了 nibName ,UITableViewCell 的創(chuàng)建方式變成了使用 UINib 來(lái)創(chuàng)建,最后返回這個(gè) cell 。
于是你可以像代碼創(chuàng)建的方式一樣愉快的創(chuàng)建 Nib 中的 UITableViewCell 了。
重要:
使用 Nib 創(chuàng)建方式,xib 里面的 Cell 標(biāo)識(shí)符務(wù)必要和方法里面的重用標(biāo)識(shí)符一致,否則很有可能會(huì)引發(fā)崩潰或未知錯(cuò)誤。