作者:Jameson Quave搓逾,原文鏈接,原文日期:2015/12/28
譯者:CMB蛹疯;校對:Cee荸镊;定稿:千葉知風(fēng)
這是一個常見的場景:你有一個 tableView 或者一個 collectionView,并且里面含有大量不同種類的內(nèi)容阅束。你想做到基于不同種類的內(nèi)容而展示不一樣的 cell
呼胚,而且這些 cell
都混合在同一個部件里(原諒我站在藝術(shù)的角度去設(shè)計),它看起來就如下圖所示:
在 Objective-C 中息裸,最典型就是使用 NSArray 來記錄 collectionView 的數(shù)據(jù)源蝇更,然后通過對比每個數(shù)據(jù)源的類型后再對 cell 進行操作沪编,現(xiàn)在看來這種方式是特別不方便的。
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"identifier" forIndexPath:indexPath];
id record = self.records[indexPath.row];
if([record isKindOfClass:[PlaythroughItem class]]) {
// ...
}
else if([record isKindOfClass:[ReviewItem class]]) {
// ...
}
else if([record isKindOfClass:[TrailerItem class]]) {
// ...
}
return cell;
}
戰(zhàn)栗吧
這并不是種類型安全的方法年扩,盡管我們在 Objective-C 中這么使用這段代碼已經(jīng)不足為奇了蚁廓。在 Swift 中,有更加好的替換方式去解決上述問題厨幻,那就是使用枚舉的 case 情況來為不同類型的項做標識相嵌,然后通過這些 case 就可以找到我們所需要的項。讓我們看看下面的例子况脆。
例子
這是一個我正在寫的休閑娛樂類 App 中需要一些不同新聞類型的 cell
的代碼:
enum NewsItem {
case Trailer(index: Int)
case Review(index: Int)
case Playthrough(index: Int)
}
索引僅僅是用來記錄數(shù)據(jù)在數(shù)據(jù)庫中位置的方法饭宾。我們采取這種索引的方法來標識所需數(shù)據(jù)在 collectionView 中位置的展示。對于特定視頻格了,我們就不需要其所關(guān)聯(lián)的所有數(shù)據(jù)了看铆,所需要的信息僅需要在 collectionView 中的 cell 點擊之后才去通過索引獲取。
我們有一個簡單的 collectionView盛末,它里面含有三個自定義的 cell
弹惦。我使用 NewsFeed.swift
文件作為這個新聞 collectionView 的主要數(shù)據(jù)源。我特別感興趣的是 cellForItemAtIndexPath
方法悄但,通過 NewsItem
枚舉來區(qū)分 record
的類型棠隐,從而產(chǎn)生相對應(yīng)的 cell
:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let record = records[indexPath.row]
switch(record) {
case .Playthrough(let index):
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PlaythroughCell", forIndexPath: indexPath) as! PlaythroughCollectionViewCell
let playthrough = MediaDB.playthroughAtIndex(index)
cell.titleLabel.text = playthrough.title
cell.lengthLabel.text = playthrough.length.prettyTime
return cell
case .Review(let index):
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ReviewCell", forIndexPath: indexPath) as! ReviewCollectionViewCell
let review = MediaDB.reviewAtIndex(index)
cell.ratingLabel.text = "\(review.rating) out of 10"
cell.titleLabel.text = review.title
return cell
case .Trailer(let index):
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("TrailerCell", forIndexPath: indexPath) as! TrailerCollectionViewCell
let trailer = MediaDB.trailerAtIndex(index)
cell.titleLabel.text = trailer.title
cell.lengthLabel.text = trailer.length.prettyTime
return cell
}
}
上面的代碼可以清晰看出,record
可以表示為 NewsItem
枚舉里三個 case 中任意一個:
enum NewsItem {
case Trailer(index: Int)
case Review(index: Int)
case Playthrough(index: Int)
}
當(dāng)我們想在 collectionView 中展示一個 cell
的時候檐嚣,我們可以通過相關(guān)的索引值去找到數(shù)據(jù)庫中所對應(yīng)的那一項宵荒。
這段代碼讓我覺得很不舒服。有許多重復(fù)代碼净嘀,尤其是 switch 顯得非常笨重报咳,在每個 case 中都做了太多事情。
但是挖藏,如果我創(chuàng)建了一個可以用在 collectionView cell 上的處理任何數(shù)據(jù)源的協(xié)議呢暑刃?鑒于每個視圖(view)都并不相同,所以我不希望這個協(xié)議在模型(model)中使用膜眠。但我可以在特定的 collectionView cell 的子類上使用它岩臣。
所以,我創(chuàng)建了一個叫做 NewsCellPresentable
協(xié)議宵膨,這個協(xié)議被自定義的 collectionView cell 所擴展:
protocol NewsCellPresentable {
func configureForIndex(index: Int)
}
extension PlaythroughCollectionViewCell: NewsCellPresentable {
func configureForIndex(index: Int) {
let playthrough = MediaDB.playthroughAtIndex(index)
self.titleLabel.text = playthrough.title
self.lengthLabel.text = playthrough.length.prettyTime
}
}
extension ReviewCollectionViewCell: NewsCellPresentable {
func configureForIndex(index: Int) {
let review = MediaDB.reviewAtIndex(index)
self.titleLabel.text = review.title
self.ratingLabel.text = "\(review.rating) out of 10"
}
}
extension TrailerCollectionViewCell: NewsCellPresentable {
func configureForIndex(index: Int) {
let trailer = MediaDB.trailerAtIndex(index)
self.titleLabel.text = trailer.title
self.lengthLabel.text = trailer.length.prettyTime
}
}
這樣寫看起來已經(jīng)很簡潔明了了〖芑眩現(xiàn)在我們回到 cellForItemAtIndexPath
方法中對代碼進行修改,修改后如下所示:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let record = records[indexPath.row]
var cell: NewsCellPresentable
switch(record) {
case .Playthrough(let index):
cell = collectionView.dequeueReusableCellWithReuseIdentifier("PlaythroughCell", forIndexPath: indexPath) as! PlaythroughCollectionViewCell
cell.configureForIndex(index)
case .Review(let index):
cell = collectionView.dequeueReusableCellWithReuseIdentifier("ReviewCell", forIndexPath: indexPath) as! ReviewCollectionViewCell
cell.configureForIndex(index)
case .Trailer(let index):
cell = collectionView.dequeueReusableCellWithReuseIdentifier("TrailerCell", forIndexPath: indexPath) as! TrailerCollectionViewCell
cell.configureForIndex(index)
}
return (cell as! MediaCollectionViewCell)
}
你覺得這種方法怎么樣辟躏?這是一種更為簡潔的方法嗎谷扣?如果你有其它不同的實現(xiàn)方法,可以直接在文章下面留言給我捎琐,或者在 Twitter 上留言給我会涎,我的用戶名是 @jquave裹匙,希望可以一起交流學(xué)習(xí)。
附言
如果你沒有數(shù)據(jù)庫底層代碼末秃,但又想寫出和我例子一樣的實例概页,你可以參照下列代碼:
class MediaDB {
class func titleForRecord(index: Int) -> String {
return "Title!!"
}
class func trailerAtIndex(index: Int) -> Trailer {
return Trailer()
}
class func reviewAtIndex(index: Int) -> Review {
return Review()
}
class func playthroughAtIndex(index: Int) -> Playthrough {
return Playthrough()
}
}
struct Trailer {
let title = "Trailer Title"
let length = 190
}
struct Review {
let title = "Review Title"
let rating = 4
}
struct Playthrough {
let title = "Playthrough Title"
let length = 9365
}
enum NewsItem {
case Trailer(index: Int)
case Review(index: Int)
case Playthrough(index: Int)
}
就個人而言,在寫后端服務(wù)和接口之前练慕,我總會做靜態(tài)值的存根惰匙。這樣會使得項目更容易迭代。