為 UITableView 擴(kuò)展一個(gè)不用注冊(cè) Cell 的方法

我們一般創(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)題:

  1. 創(chuàng)建不同類型的 Cell
    在這里我們使用的是 UITableViewCell 類型創(chuàng)建 Cell,如果需要?jiǎng)?chuàng)建不同的類型怎么辦?
  2. 創(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 變成了 nibNameUITableViewCell 的創(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ò)誤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容