原文:IGListKit Tutorial: Better UICollectionViews
作者:Ryan Nystrom
譯者:kmyhy
每個 app 都以同樣的方式開始:幾個界面忧换,幾顆按鈕凿试,一兩個 list浙宜。但隨著進度的進行以及 app 膨脹扔枫,功能開始發(fā)生變化俊鱼。你簡單的數(shù)據(jù)源開始在工期和產品經理的壓力下變得支離破碎刻像。再過一久,你留下一堆龐大得難以維護的 view controller亭引。今天绎速,IGListKit 來拯救你了!
IGListKit 專門用于解決在使用 UICollectionView 時出現(xiàn)的功能蔓延(需求蔓延)和 view controller 膨脹的問題焙蚓。用 IGListKit 創(chuàng)建列表纹冤,你可以使用非耦合組件來構建 app洒宝,飛快的刷新,支持任何類型的數(shù)據(jù)萌京。
本教程中雁歌,你將重構一個 UICollectionView 成 IGListKit,然后擴展 app知残,讓它超凡脫俗靠瞎!
開始
如果你是 NASA 的頂尖軟件工程師,正在從事最新的火星載人飛行任務求妹。開發(fā)團隊已經做好了第一版的 MarsLink app乏盐,你可以在[這里](https://koenig-media.raywenderlich.com/uploads/2016/12/Marslink_Starter.zip)下載。下載完這個項目制恍,打開 Marslink.xcworkspace 并運行 app父能。
這個 app 簡單地列出了宇航員的飛行日志。
你的任務是當團隊需要新功能時净神,添加新功能給這個 app何吝。打開 Marslink\ViewControllers\ClassicFeedViewController.swift 隨便看看,熟悉一下項目鹃唯。如果你用過 UICollectionView爱榕,你會發(fā)現(xiàn)它非常普通:
- ClassicFeedViewController 繼承了 UIViewController ,并用一個擴展實現(xiàn) 了 UICollectionViewDataSource 協(xié)議坡慌。
- viewDidLoad() 中創(chuàng)建了一個 UICollectionView, 注冊了 cell黔酥,設置了數(shù)據(jù)源,然后將它添加到視圖樹中八匠。
- loader.entries 數(shù)組保存了幾個 section絮爷,每個 section 中有兩個 cell(一個日期,一個文字)梨树。
- 日期 cell 顯示陽歷的日期,文本 cell 顯示日志內容岖寞。
- collectionView(_:layout:sizeForItemAt:) 方法返回一個固定的大小用于日期 cell抡四,以及一個根據(jù)字符串大小計算出來的 size 給文本 cell。
每件事情都很完美仗谆,但是項目 leader 帶來了一個緊急的產品升級需求:
在火星上指巡,一名宇航員擱淺了。我們需要添加一個天氣預報模塊和實時聊天隶垮。你只有48小時的時間藻雪。
JPL(噴氣推進實驗室,Jet Propulsion Laboratory) 的工程師要用到這些功能狸吞,但需要你將他們放到這個 app 中來勉耀。
如果把一名宇航員帶回家的壓力還不夠大的話指煎,NASA 的首席設計師還有一個需求,app 中每個子系統(tǒng)的升級必須是的平滑的便斥,也就是不能 reloadData()至壤。
你怎么會以為將這些模塊集成到一個偉大的 app 中并創(chuàng)建所有的轉換動畫?這名宇航員已經沒有多少土豆了枢纠!
IGListKit 介紹
UICollectionView 是一個極其強大的工具像街,與其強大一起的是負有同樣大的責任。保持你的數(shù)據(jù)源和視圖同步是極其重要的晋渺,通常崩潰就是因為這里沒搞好镰绎。
IGListKit 是一個數(shù)據(jù)驅動的 UICollectionView 框架,有 Instagram 團隊編寫木西。使用這個框架跟狱,你提供一個對象數(shù)組用于顯示到 UICollectionView 中。對于每種類型的對象户魏,需要創(chuàng)建一個 adapter驶臊,也叫做 section controller,里面包含了所有創(chuàng)建 cell 所需要的細節(jié)叼丑。
IGListKit 自動識別你的對象并在任何東西發(fā)生變化時在 UICollectonView 上執(zhí)行批量動畫刷新关翎。這樣,你就永遠不需要編寫 batch update 語句鸠信,避免這里列出的警告纵寝。
將 UICollectionView 換成 IGListKit
IGListKit 負責所有識別 collection 中發(fā)生變化,并以動畫方式刷新對應的行星立。它還能夠輕易處理針對不同的 section 使用不同的 data 和 UI 的情況爽茴。考慮到這一點绰垂,它能夠完美解決當前需求——讓我們開始吧室奏!
在 Marslink.xcworkspace 打開的情況下,右擊 ViewControllers 文件夾并選擇 New File…劲装。新建一個 Cocoa Touch Class 繼承于 UIViewController 并命名為 FeedViewController胧沫。
打開 AppDelegate.swift 找到 application(_:didFinishLaunchingWithOptions:) 方法。找到將ClassicFeedViewController() push 到 navigation controller 的行占业,將它換成:
nav.pushViewController(FeedViewController(), animated: false)
FeedViewController 現(xiàn)在成為了 root view controller绒怨。你可以保留 ClassicFeedViewController.swift 作為參考,但 FeedViewController 將作為你使用 IGListKit 實現(xiàn)一個 collection view 的地方谦疾。
運行程序南蹂,確保你能看到一個嶄新的、空白的 view controller shows念恍。
添加 Journal loader
打開 FeedViewController.swift 在 FeedViewController 頂部添加屬性:
let loader = JournalEntryLoader()
JournalEntryLoader 是一個類六剥,用于加載一個硬編碼的日志記錄到一個數(shù)組中晚顷。
在 viewDidLoad() 最后一行添加:
loader.loadLatest()
loadLatest() 是 JournalEntryLoader 中的方法,加載最新的日志記錄仗考。
加入 collection view
現(xiàn)在來添加某些 IGListKit 的特殊控件到 view controller 中了音同。在這樣做之前,你需要引入這個框架秃嗜。在 FeedViewController.swift 頂部加入 import 語句:
import IGListKit
注意:本示例項目使用 CocoaPods 管理依賴权均。IGListKit 是 Objective-C 些的,因此需要在橋接頭文件中用 #import 手動添加到你的項目锅锨。
在 FeedViewController 頂部添加一個 collectionView 常量:
// 1
let collectionView: IGListCollectionView = {
// 2
let view = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
// 3
view.backgroundColor = UIColor.black
return view
}()
- IGListKit 使用了 IGListCollectionView, 這是一個 UICollectionView 的子類叽赊,添加了某些功能并修復了某些缺陷。
- 一開始用一個大小為 0 的 frame必搞,因為 view 都還沒創(chuàng)建必指。它也使用了 UICollectionViewFlowLayout ,就像 ClassicFeedViewController 一樣恕洲。
- 背景色設為 NASA-認可的黑色塔橡。
在 viewDidLoad() 方法最后一句加入:
view.addSubview(collectionView)
這將新的 collectionView 添加到 controller 的 view。
在 viewDidLoad() 下面加入:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
viewDidLayoutSubviews() 是一個覆蓋方法霜第,將 collectionView的 frame 設為view 的 bounds葛家。
IGListAdapter 和數(shù)據(jù)源
使用 UICollectionView,你需要某個數(shù)據(jù)源實現(xiàn) UICollectionViewDataSource 協(xié)議泌类。它的作用是返回 section 和 row 的數(shù)目以及每個 cell癞谒。
在 IGListKit 中,你使用一個 GListAdapter 來控制 collection view刃榨。你仍然需要一個數(shù)據(jù)源來實現(xiàn) IGListAdapterDataSource 協(xié)議弹砚,但不是返回數(shù)字或 cell,你需要提供數(shù)組和 controllers(后面會細講)枢希。
首先桌吃,在 FeedViewController.swift 在頭部加入:
lazy var adapter: IGListAdapter = {
return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0)
}()
這創(chuàng)建了一個延遲加載的 IGListAdapter 變量。這個初始化方法有 3 個參數(shù):
- updater 是一個實現(xiàn)了 IGListUpdatingDelegate 協(xié)議的對象, 它負責處理 row 和 section 的刷新晴玖。IGListAdapterUpdater 有一個默認實現(xiàn)读存,剛好給我們用。
- viewController 是一個 UIViewController 呕屎,它擁有這個 adapter。 這個 view controller 后面會用于導航到別的 view controllers敬察。
- workingRangeSize 是 warking range 的大小秀睛。允許你為那些不在可見范圍內的 section 準備內容。
注意:working range 是另一個高級主題莲祸,本教程不會涉及蹂安。但是在 IGListKit 的代碼庫中有它豐富的文檔甚至一個示例 app椭迎。
在 viewDidLoad() 方法最后一行添加:
adapter.collectionView = collectionView
adapter.dataSource = self
這會將 collectionView 和 adapter 聯(lián)系在一起。還將 self 設置為 adapter 的數(shù)據(jù)源——這會報一個錯誤田盈,因為你還沒有實現(xiàn) IGListAdapterDataSource 協(xié)議畜号。
要解決這個錯誤,聲明一個 FeedViewController 擴展以實現(xiàn) IGListAdapterDataSource 協(xié)議允瞧。在文件最后添加:
extension FeedViewController: IGListAdapterDataSource {
// 1
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
return loader.entries
}
// 2
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
return IGListSectionController()
}
// 3
func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil }
}
FeedViewController 現(xiàn)在采用了 IGListAdapterDataSource 協(xié)議并實現(xiàn)了 3 個必須的方法:
- objects(for:) 返回一個數(shù)據(jù)對象組成的數(shù)組简软,這些對象將顯示在 collection view。這里返回了loader.entries述暂,因為它包含了日志記錄痹升。
- 對于每個數(shù)據(jù)對象,listAdapter(_:sectionControllerFor:) 方法必須返回一個新的 section conroller 實例∑杈拢現(xiàn)在疼蛾,你返回了一個空的 IGListSectionController以解除編譯器的抱怨——等會,你會修改這里艺配,返回一個自定義的日志的 section controller察郁。
- emptyView(for:) 返回一個 view,它將在 List 為空時顯示转唉。NASA 給的時間比較倉促皮钠,他們沒有為這個功能做預算。
創(chuàng)建第一個 Section Controller
一個 section controller 是一個抽象的對象酝掩,指定一個數(shù)據(jù)對象鳞芙,它負責配置和管理 CollectionView 中的一個 section 中的 cell。這個概念類似于一個用于配置一個 view 的 view-model:數(shù)據(jù)對象就是 view-model期虾,而 cell 則是 view原朝,section controller 則是二者之間的粘合劑。
在 IGListKit 中镶苞,你根據(jù)不同類型的數(shù)據(jù)的類型和特性創(chuàng)建不同的 section controller喳坠。JPL 的工程師已經放入了一個 JournalEntry model,你只需要創(chuàng)建能夠處理這個 Model 的 section controller 就行了茂蚓。
在 SectionController 文件夾上右擊壕鹉,選擇 New File…,創(chuàng)建一個 Cocoa Touch Class 名為 JournalSectionController 聋涨,繼承 IGListSectionController晾浴。
Xcode 不會自動引入第三方框架,因此在 JournalSectionController.swift 需要添加:
import IGListKit
為 JournalSectionController 添加如下屬性:
var entry: JournalEntry!
let solFormatter = SolFormatter()
JournalEntry 是一個 model 類牍白,在實現(xiàn)數(shù)據(jù)源時你會用到它脊凰。SolFormatter 類提供了將日期轉換為太陽歷格式的方法。很快你會用到它們茂腥。
在 JournalSectionController 中狸涌,覆蓋 init() 方法:
override init() {
super.init()
inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
}
如果不這樣做切省,section 中的 cell 會一個緊挨著一個。這個方法在每個 JournalSectionController 對象的下方增加 15 個像素的間距帕胆。
你的 section controller需要實現(xiàn) IGListSectionType 協(xié)議才能被 IGListKit 所用朝捆。在文件最后添加一個擴展:
extension JournalSectionController: IGListSectionType {
func numberOfItems() -> Int {
return 2
}
func sizeForItem(at index: Int) -> CGSize {
return .zero
}
func cellForItem(at index: Int) -> UICollectionViewCell {
return UICollectionViewCell()
}
func didUpdate(to object: Any) {
}
func didSelectItem(at index: Int) {}
}
注意: IGListKit 非常依賴 required 協(xié)議方法。但在這些方法中你可以空實現(xiàn)懒豹,或者返回 nil芙盘,以免收到“缺少方法”的警告或運行時報錯。這樣歼捐,在使用 IGListKit 時就不容易出錯何陆。
你實現(xiàn)了 IGListSectionType 協(xié)議的 4 個 required 方法。
所有方法都是無存根的實現(xiàn)豹储,除了 numberOfItems() 方法— 返回了一個 2 贷盲,表示一個日期和一個文本字符串。你可以回到 ClassicFeedViewController.swift 看看剥扣,在collectionView( _:numberOfItemsInSection:) 方法中你返回的也是 2巩剖。這兩個方法基本上是一樣的。
在 didUpdate(to:)方法中加入:
entry = object as? JournalEntry
didUpdate(to:) 用于將一個對象傳給 section controller钠怯。注意在任何 cell 協(xié)議方法之前調用佳魔。這里,你把接收到的 object 參數(shù)賦給 entry晦炊。
注意:在一個 section controller 的生命周期中鞠鲜,對象有可能會被改變多次。這只會在啟用了 IGListKit 的更高級的特性時候發(fā)生断国,比如自定義模型的 Diffing 算法贤姆。在本教程中你不需要擔心 Diffing。
現(xiàn)在你有一些數(shù)據(jù)了稳衬,你可以開始配置你的 cell 了霞捡。將 cellForItem(at:) 方法替換為:
// 1
let cellClass: AnyClass = index == 0 ? JournalEntryDateCell.self : JournalEntryCell.self
// 2
let cell = collectionContext!.dequeueReusableCell(of: cellClass, for: self, at: index)
// 3
if let cell = cell as? JournalEntryDateCell {
cell.label.text = "SOL \(solFormatter.sols(fromDate: entry.date))"
} else if let cell = cell as? JournalEntryCell {
cell.label.text = entry.text
}
return cell
cellForItem(at:) 方法詢問到 section 的某個 cell(指定的 Index)時調用。以上代碼解釋如下:
- 如果 index 是第一個薄疚,返回 JournalEntryDateCell 單元格碧信,否則返回 JournalEntryCell 單元格。日志數(shù)據(jù)總是先顯示日期街夭,然后才是文本砰碴。
- 從緩存中取出一個 cell,dequeue 時需要指定 cell 的類型板丽,一個 section controller 對象衣式,以及 index。
- 根據(jù) cell 的類型檐什,用你先前在 didUpdate(to objectd:)方法中設置的 entry 來配置 cell碴卧。
然后,將 sizeForItem(at:) 方法替換為:
// 1
guard let context = collectionContext, let entry = entry else { return .zero }
// 2
let width = context.containerSize.width
// 3
if index == 0 {
return CGSize(width: width, height: 30)
} else {
return JournalEntryCell.cellSize(width: width, text: entry.text)
}
collectionContext 是一個弱引用乃正,同時是 nullabel 的住册。雖然它永遠不可能為空,但最好是做一個前置條件判斷瓮具,使用 Swift 的 guard 語句就行了荧飞。
IGListCollectionContext 是一個上下文對象,保存了這個 section view 中用到的 adapter名党、collecton view叹阔、以及 view controller。這里我們需要獲取容器 container 的寬度传睹。
如果是第一個 index(即日期 cell)耳幢,返回一個寬度等于 container 寬度,高度等 30 像素的 size欧啤。否則睛藻,使用 cell 的助手方法根據(jù) cell 文本計算 size。
最后一個方法是 didSelectItem(at:)邢隧,這個方法在點擊某個 cell 時調用店印。這是一個 required 方法,你必須實現(xiàn)它倒慧,但如果你不想進行任何處理的話按摘,可以空實現(xiàn)。
這種 dequeue 不同類型的 cell纫谅、對 cell 進行不同配置和并返回不同 size 的套路和你之前使用 UICollectionView 的套路并無不同炫贤。你可以回去 ClassicFeedViewController 看看,這些代碼中有許多都很相似系宜!
現(xiàn)在你擁有了一個 section controller照激,它接收一個 JournalEntry 對象,并返回連個 cell 和 size盹牧。接下來我們就來使用它俩垃。
打開 FeedViewController.swift, 將 listAdapter(_:sectionControllerFor:) 方法替換為:
return JournalSectionController()
現(xiàn)在,這個方法返回了新的 Journal Section Controller 對象汰寓。
運行程序口柳,你將看到一個航空日志的列表!
添加消息
JPL 工程師很高興你能這么快就完成了修改有滑,但他們還需要和那個倒霉的宇航員建立聯(lián)系跃闹。他們要你盡快將消息模塊也集成進去。
在添加任何視圖之前,首先的一件事情就是數(shù)據(jù)望艺。
打開 FeedViewController.swift 添加一個屬性:
let pathfinder = Pathfinder()
PathFinder() 扮演了消息系統(tǒng)苛秕,并代表了火星上宇航員的探路車。
在 IGListAdapterDataSource 擴展中找到 objects(for:) 找默,將內容修改為:
var items: [IGListDiffable] = pathfinder.messages
items += loader.entries as [IGListDiffable]
return items
你可能想起來了艇劫,這個方法負責將數(shù)據(jù)源對象提供給 IGListAdapter。這里進行了一些修改惩激,將 pathfinder.messages 添加到 items 中店煞,以便為新的 section controller 提供消息數(shù)據(jù)。
注意:你必須轉換消息數(shù)組以免編譯器報錯风钻。這些對象已經實現(xiàn)了 IGListDiffable 協(xié)議顷蟀。
在 SectionControllers 文件夾上右擊,創(chuàng)建一個新的 IGListSectionController 子類名為 MessageSectionController骡技。在文件頭部引入 IGListKit:
import IGListKit
讓編譯器不報錯之后鸣个,保持剩下的內容不變。
回到 FeedViewController.swift 修改 IGListAdapterDataSource 擴展中的 listAdapter(_:sectionControllerFor:) 方法為:
if object is Message {
return MessageSectionController()
} else {
return JournalSectionController()
}
現(xiàn)在哮兰,如果數(shù)據(jù)對象的類型是 Message,毛萌,我們會返回一個新的 Message Secdtion Controller。
JPL 團隊需要你在創(chuàng)建 MessageSectionController 時滿足下列需求:
- 接收 Message 消息
- 底部間距 15 像素
- 通過 MessageCell.cellSize(width:text:) 函數(shù)返回一個 cell 的 size
- dequeue 并配置一個 MessageCell喝滞,并用 Message 對象的 text 和 user.name 屬性填充 Label阁将。
試試看!如果你需要幫助右遭,JPL 團隊也在下面的提供了參考答案:
答案: MessageSectionController
import IGListKit
class MessageSectionController: IGListSectionController {
var message: Message!
override init() {
super.init()
inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
}
}
extension MessageSectionController: IGListSectionType {
func numberOfItems() -> Int {
return 1
}
func sizeForItem(at index: Int) -> CGSize {
guard let context = collectionContext else { return .zero }
return MessageCell.cellSize(width: context.containerSize.width, text: message.text)
}
func cellForItem(at index: Int) -> UICollectionViewCell {
let cell = collectionContext?.dequeueReusableCell(of: MessageCell.self, for: self, at: index) as! MessageCell
cell.messageLabel.text = message.text
cell.titleLabel.text = message.user.name.uppercased()
return cell
}
func didUpdate(to object: Any) {
message = object as? Message
}
func didSelectItem(at index: Int) {}
}
當你寫完時做盅,運行 app,看看將消息集成后的效果窘哈!
火星天氣預報
我們的宇航員需要知道當前天氣以便避開某些東西比如沙塵暴吹榴。JPL 編寫了一個顯示當前天氣的模塊。但是那個信息有點多滚婉,因此他們要求只有在用戶點擊之后才顯示天氣信息图筹。
編寫最后一個 sectioncontroller,名為 WeatherSecdtionController∪酶梗現(xiàn)在這個類中定義一個構造函數(shù)和幾個變量:
import IGListKit
class WeatherSectionController: IGListSectionController {
// 1
var weather: Weather!
// 2
var expanded = false
override init() {
super.init()
// 3
inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
}
}
這個 section controller 會從 didUpdate(to:) 方法中接收到一個 Weather 對象远剩。
expanded 是一個布爾值,用于保存天氣 section 是否被展開骇窍。默認為 false,這樣它下面的 cell 一開始是折疊的瓜晤。
和另外幾個 section 一樣,底部 inset 設置為 15 像素腹纳。
加一個 IGListSectionType 擴展痢掠,實現(xiàn) 3 個 required 方法:
extension WeatherSectionController: IGListSectionType {
// 1
func didUpdate(to object: Any) {
weather = object as? Weather
}
// 2
func numberOfItems() -> Int {
return expanded ? 5 : 1
}
// 3
func sizeForItem(at index: Int) -> CGSize {
guard let context = collectionContext else { return .zero }
let width = context.containerSize.width
if index == 0 {
return CGSize(width: width, height: 70)
} else {
return CGSize(width: width, height: 40)
}
}
}
- 在 didUpdate(to:) 方法中驱犹,你保存了傳入的 Weather 對象。
- 如果天氣被展開足画,numberOfItems() 返回 5 個 cell雄驹,這樣它會包含天氣數(shù)據(jù)的每個部分。如果不是展開狀態(tài)锌云,只返回一個用于顯示占位內容的 cell荠医。
- 第一個 cell 會比其他 cell 大一點,因為它是一個 Header桑涎。沒有必要判斷展開狀態(tài),因為 Header cell 只會顯示在第一個 cell兼贡。
然后你需要實現(xiàn) cellForItem(at:)方法來配置 weather cell攻冷。有幾個細節(jié)需要注意:
第一個 cell 是 WeatherSummaryCell 類型,其他 cell 是 WeatherDetailCell 類型遍希。
通過 cell.setExpanded(_:) 方法來配置 WeatherSummaryCell等曼。
-
配置 4 個不同的 WeatherDetailCell 用下列 title 和 detail 標簽:
- “Sunrise” - weather.sunrise
- “Sunset” - weather.sunset
- “High” - “(weather.high) C”
- “Low” - “(weather.low) C”
試著配置一下這個 cell! 參考答案如下。
func cellForItem(at index: Int) -> UICollectionViewCell {
let cellClass: AnyClass = index == 0 ? WeatherSummaryCell.self : WeatherDetailCell.self
let cell = collectionContext!.dequeueReusableCell(of: cellClass, for: self, at: index)
if let cell = cell as? WeatherSummaryCell {
cell.setExpanded(expanded)
} else if let cell = cell as? WeatherDetailCell {
let title: String, detail: String
switch index {
case 1:
title = "SUNRISE"
detail = weather.sunrise
case 2:
title = "SUNSET"
detail = weather.sunset
case 3:
title = "HIGH"
detail = "\(weather.high) C"
case 4:
title = "LOW"
detail = "\(weather.low) C"
default:
title = "n/a"
detail = "n/a"
}
cell.titleLabel.text = title
cell.detailLabel.text = detail
}
return cell
}
最后還有最后一件事情凿蒜,當 cell 被點擊時禁谦,切換 section 的展開狀態(tài)并刷新 cell。在 IGListSectionType 擴展后實現(xiàn)這個 required 協(xié)議方法:
func didSelectItem(at index: Int) {
expanded = !expanded
collectionContext?.reload(self)
}
reload() 方法重新加載整個 section废封。當 section controller 中的 cell 的數(shù)目或者內容發(fā)生變化時州泊,你可以調用這個方法。因此我們通過 numberOfItems() 方法切換 section 的展開狀態(tài)漂洋,在這個方法中根據(jù) expanded 的值來添加或減少 cell 的數(shù)目遥皂。
回到 FeedViewController.swift, 在頭部加入屬性:
let wxScanner = WxScanner()
WxScanner 是一個用于天氣情況的模型對象。
然后刽漂,修改 IGListAdapterDataSource 擴展中的 objects(for:) 方法:
// 1
var items: [IGListDiffable] = [wxScanner.currentWeather]
items += loader.entries as [IGListDiffable]
items += pathfinder.messages as [IGListDiffable]
// 2
return items.sorted(by: { (left: Any, right: Any) -> Bool in
if let left = left as? DateSortable, let right = right as? DateSortable {
return left.date > right.date
}
return false
})
我們修改了數(shù)據(jù)源方法演训,讓它增加 currentWeather 的數(shù)據(jù)。代碼解釋如下:
- 將 currentWeather 添加到 items 數(shù)組贝咙。
- 讓所有數(shù)據(jù)實現(xiàn) DataSortable 協(xié)議样悟,以便用于排序。這樣數(shù)據(jù)會按照日期前后順序排列庭猩。
最后窟她,修改 listAdapter(_:sectionControllerFor:) 方法:
if object is Message {
return MessageSectionController()
} else if object is Weather {
return WeatherSectionController()
} else {
return JournalSectionController()
}
現(xiàn)在,當 object 是 Weather 類型時眯娱,返回一個 WeatherSectionController礁苗。
運行 app。你會在頂部看到新的天氣對象徙缴。點擊這個 section试伙,展開和收起它嘁信!
更新操作
JPL 對你的進度相當?shù)臐M意!當你在工作時疏叨,NASA 的 director 組織了對宇航員的營救工作谢揪,要求他起飛并攔截另一艘飛船!這是一次復雜的起飛哲思,他起飛的時間必須十分精確民逼。
JPL 工程師擴展了消息模塊,加入了實時聊天功能秀又,要求你集成它单寂。
打開 FeedViewController.swift 在 viewDidLoad() 方法最后一行加入:
pathfinder.delegate = self
pathfinder.connect()
這個 Pathfinder 模塊增加了實時聊天支持。你需要做的僅僅是連接這個模塊并處理委托事件吐辙。
在文件底部增加新的擴展:
extension FeedViewController: PathfinderDelegate {
func pathfinderDidUpdateMessages(pathfinder: Pathfinder) {
adapter.performUpdates(animated: true)
}
}
FeedViewController 現(xiàn)在實現(xiàn)了 PathfinderDelegate 協(xié)議宣决。只有一個 performUpdates(animated:completion:) 方法,用于告訴 IGListAdapter 查詢數(shù)據(jù)源中的新對象并刷新UI昏苏。這個方法用于處理對象被刪除尊沸、更新、移動或插入的情況贤惯。
運行 app洼专,你會看到標題上消息正在刷新!你只不過是為 IGListKit 添加了一個方法孵构,用于說明數(shù)據(jù)源發(fā)生了什么變化屁商,并在收到新數(shù)據(jù)時執(zhí)行修改動畫。
現(xiàn)在浦译,你所需要做的僅僅是將最新版本發(fā)給宇航員棒假,他就能回家了!干得不錯!
結束
在這里下載最后完成的項目精盅。
在幫助一位擱淺的宇航員回家的同時帽哑,你學習了 IGListKit 的基本功能:section controller、adapter叹俏、以及如何將它們組合在一起妻枕。還有其他重要的功能,比如 supplementary view 和 display 事件粘驰。
你可以閱讀 Instagram 放在 Realm 上關于為什么要編寫 IGListKit 的討論屡谐。這個討論中提到了許多在編寫 app 時經常遇到在 UICollecitonView 中出現(xiàn)的問題。
如果你對參加 IGListKit 有興趣蝌数,開發(fā)團隊為了便于讓你開始愕掏,在 Github 上創(chuàng)建了一個 starter-task 的tag。