使用泛型來優(yōu)化 TableView Cells 的使用體驗

作者:Olivier Halligon害捕,原文鏈接,原文日期:2016-01-06
譯者:walkingway曙痘;校對:小鍋腰耙;定稿:numbbbbb

各位新年快樂????????! 2016 年第一篇博客我想分享一個非常有用的技巧源请,那就是向大家展示 Swift 泛型的強大枪芒,以及方便地使用泛型來處理 UITableViewCellsUICollectionViewCells

介紹

我不喜歡使用字符串做標(biāo)識符谁尸,我認(rèn)為使用常量要比字符串好很多舅踪。

但是,當(dāng)涉及到 UITableViewCellUICollectionViewCell 以及他們的重用標(biāo)識符(reuseIdentifiers)時良蛮,我想采用一種更加魔幻的解決方案:『使用 Swift 的泛型 + Mixins 的方式』抽碌,下面讓我們摒住呼吸,見證奇跡的時刻决瞳。

魔法時刻

我的想法是在 UITableViewCell(或 UICollectionViewCell)的子類中將 reuseIdentifier 聲明為一個靜態(tài)常量货徙,然后用它使這個 cell 的實例對外部透明化(即,不用顯式地使用 reuseIdentifer 來實例化 cell)皮胡。

我們首先聲明一個協(xié)議痴颊,以便于稍后能夠將其作為 Mixin 來使用:

protocol Reusable: class {
  static var reuseIdentifier: String { get }
}

extension Reusable {
  static var reuseIdentifier: String {
     // 我喜歡使用類名來作為標(biāo)識符
     // 所以這里可以用類名返回一個默認(rèn)值
    return String(Self)
  }
}

當(dāng)我們使用泛型實現(xiàn) dequeueReusableCell(…) 方法時,魔法出現(xiàn)了:

func dequeueReusableCell<T: Reusable>(indexPath indexPath: NSIndexPath) -> T {
  return self.dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
}

得益于 Swift 的類型推斷屡贺,這個方法將使用調(diào)用點的上下文來推斷 T 的實際類型蠢棱,因此這個類型可以被看做方法實現(xiàn)中的『復(fù)古注入』(retro-injected)!?

let cell = tableView.dequeueReusableCell(indexPath: indexPath) as MyCustomCell

注意觀察 reuseIdentifier 在內(nèi)部的使用方法...完全由編譯器看到的返回類型決定甩栈!那就是我說的類型“復(fù)古注射(retro-injected)”泻仙,以及為什么我超??它的原因

譯者注:Swift 的類型在編譯時刻就確定了,所以當(dāng)你寫下 as MyCustomCell 后量没,cell 的類型即泛型 T 的具體類型就確定了

很美妙不是嗎饰豺?

更進一步

當(dāng)然,除了 dequeueReusableCellWithIdentifier 之外允蜈,你同樣可以把這個方法用在 registerNib(_, forCellWithReuseIdentifier:)UICollectionViewCells,supplementary 視圖上蒿柳。

UITableViewCellsUICollectionViewCells 都能通過類名(registerClass(_, forCellWithReuseIdentifier:)) 或 nib(registerNib(_, forCellWithReuseIdentifier:))的方式進行注冊饶套。如果存在 nib,我們將在協(xié)議中添加一個類型屬性(static var nib: UINib?)垒探,然后使用這個 nib 注冊 cell妓蛮;如果 nib 不存在則使用類注冊。

代碼

這里是我在項目中實際使用的代碼:

[2016-01-20 修改]

現(xiàn)在可以在Github上看到這個代碼了圾叼!

import UIKit

protocol Reusable: class {
  static var reuseIdentifier: String { get }
  static var nib: UINib? { get }
}

extension Reusable {
  static var reuseIdentifier: String { return String(Self) }
  static var nib: UINib? { return nil }
}

extension UITableView {
  func registerReusableCell<T: UITableViewCell where T: Reusable>(_: T.Type) {
    if let nib = T.nib {
      self.registerNib(nib, forCellReuseIdentifier: T.reuseIdentifier)
    } else {
      self.registerClass(T.self, forCellReuseIdentifier: T.reuseIdentifier)
    }
  }

  func dequeueReusableCell<T: UITableViewCell where T: Reusable>(indexPath indexPath: NSIndexPath) -> T {
    return self.dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
  }

  func registerReusableHeaderFooterView<T: UITableViewHeaderFooterView where T: Reusable>(_: T.Type) {
    if let nib = T.nib {
      self.registerNib(nib, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
    } else {
      self.registerClass(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
    }
  }

  func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView where T: Reusable>() -> T? {
    return self.dequeueReusableHeaderFooterViewWithIdentifier(T.reuseIdentifier) as! T?
  }
}

extension UICollectionView {
  func registerReusableCell<T: UICollectionViewCell where T: Reusable>(_: T.Type) {
    if let nib = T.nib {
      self.registerNib(nib, forCellWithReuseIdentifier: T.reuseIdentifier)
    } else {
      self.registerClass(T.self, forCellWithReuseIdentifier: T.reuseIdentifier)
    }
  }

  func dequeueReusableCell<T: UICollectionViewCell where T: Reusable>(indexPath indexPath: NSIndexPath) -> T {
    return self.dequeueReusableCellWithReuseIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
  }

  func registerReusableSupplementaryView<T: Reusable>(elementKind: String, _: T.Type) {
    if let nib = T.nib {
      self.registerNib(nib, forSupplementaryViewOfKind: elementKind, withReuseIdentifier: T.reuseIdentifier)
    } else {
      self.registerClass(T.self, forSupplementaryViewOfKind: elementKind, withReuseIdentifier: T.reuseIdentifier)
    }
  }

  func dequeueReusableSupplementaryView<T: UICollectionViewCell where T: Reusable>(elementKind: String, indexPath: NSIndexPath) -> T {
    return self.dequeueReusableSupplementaryViewOfKind(elementKind, withReuseIdentifier: T.reuseIdentifier, forIndexPath: indexPath) as! T
  }
}

示例用法

下面演示如何聲明一個 UITableViewCell 的子類:

class CodeBasedCustomCell: UITableViewCell, Reusable {
  // By default this cell will have a reuseIdentifier or "MyCustomCell"
  // unless you provide an alternative implementation of `var reuseIdentifier`
  // ...
}

class NibBasedCustomCell: UITableViewCell, Reusable {
  // Here we provide a nib for this cell class
  // (instead of relying of the protocol's default implementation)
  static var nib: UINib? {
    return UINib(nibName: String(NibBasedCustomCell.self), bundle: nil)
  }
  // ...
}

然后在 UITableViewDelegate/UITableViewDataSource 中使用他們:

class MyTableViewController: UITableViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    tableView.registerReusableCell(CodeBasedCustomCell.self) // This will register using the class without using a UINib
    tableView.registerReusableCell(NibBasedCustomCell.self) // This will register using NibBasedCustomCell.xib
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell: UITableViewCell
    if indexPath.section == 0 {
      cell = tableView.dequeueReusableCell(indexPath: indexPath) as CodeBasedCustomCell
    } else {
      cell = tableView.dequeueReusableCell(indexPath: indexPath) as NibBasedCustomCell
    }
    return cell
  }
}

另一種解決方案

有些人傾向于將 Reusable 協(xié)議拆分成兩個不同的協(xié)議蛤克,用于區(qū)分基于 nib 創(chuàng)建與基于 class 創(chuàng)建的 cell:

protocol Reusable: class {
  static var reuseIdentifier: String { get }
}
extension Reusable {
  static var reuseIdentifier: String { return String(Self) }
}

protocol NibReusable: Reusable {
  static var nib: UINib { get }
}
extension NibReusable {
  static var nib: UINib {
    return UINib(nibName: String(Self), bundle: nil)
  }
}

這樣基于 nib 創(chuàng)建的 cell 也能使用默認(rèn)實現(xiàn) —— 因此不用在子類中再重新實現(xiàn)一遍捺癞。

但是這也會迫使你在 UITableViewUICollectionView上添加更多的實現(xiàn)方法(每個協(xié)議中添加一個實現(xiàn)),所以嘛...這其中的平衡還要靠你自己來把握 ???

福利:現(xiàn)在你能通過 Cocoapods 使用這個庫啦 ??

2016-01-20 增加

以上代碼和示例构挤,我已經(jīng)上傳至 GitHub髓介,并且通過 Swift Package 以及 CocoaPod 發(fā)布了,現(xiàn)在可以很方便地添加到你的工程中筋现。

隨時歡迎各種 PR 來共同改進這個項目 ??

--

最后希望你喜歡這一技巧唐础,我們下次再見嘍!??

本文由 SwiftGG 翻譯組翻譯矾飞,已經(jīng)獲得作者翻譯授權(quán)一膨,最新文章請訪問 http://swift.gg

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洒沦,一起剝皮案震驚了整個濱河市豹绪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌申眼,老刑警劉巖瞒津,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異豺型,居然都是意外死亡仲智,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門姻氨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钓辆,“玉大人,你說我怎么就攤上這事肴焊∏傲” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵娶眷,是天一觀的道長似嗤。 經(jīng)常有香客問我,道長届宠,這世上最難降的妖魔是什么烁落? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮豌注,結(jié)果婚禮上伤塌,老公的妹妹穿的比我還像新娘。我一直安慰自己轧铁,他們只是感情好每聪,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般药薯。 火紅的嫁衣襯著肌膚如雪绑洛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天童本,我揣著相機與錄音真屯,去河邊找鬼。 笑死巾陕,一個胖子當(dāng)著我的面吹牛讨跟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鄙煤,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼晾匠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梯刚?” 一聲冷哼從身側(cè)響起凉馆,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亡资,沒想到半個月后澜共,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡锥腻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年嗦董,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘦黑。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡京革,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出幸斥,到底是詐尸還是另有隱情匹摇,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布甲葬,位于F島的核電站廊勃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏经窖。R本人自食惡果不足惜坡垫,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望画侣。 院中可真熱鬧葛虐,春花似錦、人聲如沸棉钧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宪卿。三九已至的诵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佑钾,已是汗流浹背西疤。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留休溶,地道東北人代赁。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像兽掰,于是被迫代替她去往敵國和親芭碍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫孽尽、插件窖壕、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,120評論 4 61
  • 定義:background-size 屬性規(guī)定背景圖像的尺寸。 兼容性:IE9+杉女、Firefox 4+瞻讽、Opera...
    Maggie_77閱讀 1,321評論 0 0
  • 昆明的天,時常都是藍藍的熏挎,漂著幾朵白色的云速勇,明朗的不像話。 那一年坎拐,我拖著行李箱上了去成都的火車烦磁,一路...
    聞是然也閱讀 241評論 0 0
  • 我,我是來道歉的廉白,我不該把兩顆星星和手鏈搞丟的个初,之前不敢跟你說,怪我自己太大意……可是掉了之后真的很傷心猴蹂,可我也不...
    asdfhhjj閱讀 410評論 0 0
  • ? 原來他是《爆裂鼓手》的導(dǎo)演院溺,他執(zhí)導(dǎo)的影片都在閃閃發(fā)光,他的電影里總在講述夢想磅轻,LA LA LAND依然珍逸。 夢想...
    午夜石榴閱讀 627評論 0 1