varentry: JournalEntry!letsolFormatter = SolFormatter()本文翻譯自 Ryan Nystrom, IGListKit Tutorial: Better UICollectionViews.
大部分App都以相同的方式開始: ?一些視圖, 幾個(gè)按鈕, 一到兩個(gè)列表. 但是隨著時(shí)間的推移,app也在慢慢成長(zhǎng), 很多特性在以各自的方式蛻變. ?在最后期限和產(chǎn)品經(jīng)理的壓力下, 也許你會(huì)對(duì)著你空白的數(shù)據(jù)源感到崩潰, 為了完成任務(wù), 最后留下大量的需要維護(hù)的控制器視圖. 快點(diǎn)開始采用IGListKit來減輕你的負(fù)擔(dān)吧
使用IGListKit 有效避免了使用UICollectionView的產(chǎn)生的大量試圖控制器,通過IGListKit創(chuàng)建列表,你可以使用解耦組件構(gòu)建APP,快速迭代,并支持任何類型的數(shù)據(jù)類型
在這個(gè)例子中, 我們將基于UICollectionView以使用IGListKit,然后擴(kuò)展該應(yīng)用程序最終完成
開始
您是美國(guó)航空航天局頂尖的軟件工程師之一,也是最新的火星任務(wù)。 該團(tuán)隊(duì)已經(jīng)構(gòu)建了Marslink應(yīng)用程序的第一個(gè)版本,您可以在這里下載。 項(xiàng)目下載后,打開Marslink.xcworkspace并運(yùn)行應(yīng)用程序。
目前為止,應(yīng)用程序只顯示一份宇航員日列表。
只船員需要新的功能,您都需要為此應(yīng)用添加功能。通過打開Marslink \ ViewControllerers \ ClassicFeedViewController.swift 熟悉項(xiàng)目。如果你曾經(jīng)使用過UICollectionView,你看起來非常標(biāo)準(zhǔn):
ClassicFeedViewController是一個(gè)UIViewController子類,在擴(kuò)展中實(shí)現(xiàn)UICollectionViewDataSource。
viewDidLoad()創(chuàng)建一個(gè)UICollectionView,注冊(cè)單元格,設(shè)置數(shù)據(jù)源,并將其添加到視圖層次結(jié)構(gòu)中。
loader.entries數(shù)組決定了section的數(shù)量,每個(gè)部分只有兩個(gè)單元格(一個(gè)用于日期,一個(gè)用于文本)。
日期單元格使用日志文本配置Sol日期和文本輸入單元格。
collectionView(_:layout:sizeForItemAt :)為日期單元格返回固定大小,并計(jì)算實(shí)際條目的文本大小。
一切似乎工作正常,但產(chǎn)品經(jīng)理帶有一些緊急的產(chǎn)品更新請(qǐng)求:
一名宇航員剛剛被困在火星上。 我們需要您添加一個(gè)天氣模塊和實(shí)時(shí)聊天。 你有48個(gè)小時(shí)。
來自JPL的工程師提供了一些模塊,但是他們需要你幫助把它們放在應(yīng)用程序中。
如果把宇航員帶回家的所有壓力都不夠,美國(guó)航空航天局的首席設(shè)計(jì)師只是向你們傳遞了每個(gè)子系統(tǒng)在應(yīng)用程序中的更新必須被動(dòng)畫化的要求,這意味著沒有reloadData()。
怎么才能把所有的組件加入程序中, 并且讓所有的轉(zhuǎn)化有動(dòng)畫呢?
介紹
雖然UICollectionView是一個(gè)非常強(qiáng)大的工具,具有強(qiáng)大的組件和庫。 保持?jǐn)?shù)據(jù)源和視圖同步是非常重要的,崩潰通常是由于斷開連接造成的。
IGListKit是由Instagram在Instagram創(chuàng)建的數(shù)據(jù)驅(qū)動(dòng)的UICollectionView框架。 通過這個(gè)框架,您可以提供一系列在UICollectionView中顯示的對(duì)象。 對(duì)于每種類型的對(duì)象,適配器創(chuàng)建一個(gè)名為section controller的內(nèi)容,該控件具有創(chuàng)建單元格的所有細(xì)節(jié)。
IGListKit會(huì)自動(dòng)分辨對(duì)象,并在UICollectionView上執(zhí)行動(dòng)畫批量更新以進(jìn)行任何更改。 這樣你就不必自己編寫批量更新,避免在這里列出的問題。
使用
IGListKit能很好的識(shí)別集合中的更改以及使用動(dòng)畫更新相應(yīng)行。它還可以輕松處理具有不同數(shù)據(jù)和UI的多個(gè)部分。考慮到這一點(diǎn),它是對(duì)新批次的要求的完美解決方案 - 現(xiàn)在開始實(shí)現(xiàn)他!
Marslink.xcworkspace仍然打開,右鍵單擊ViewControllers組,然后選擇新建文件...。添加一個(gè)新的Cocoa Touch Class,它將UIViewController命名為FeedViewController。
打開AppDelegate.swift并找到應(yīng)用程序(_:didFinishLaunchingWithOptions :)方法。找到將ClassicFeedViewController()推送到導(dǎo)航控制器并將其替換為的行:
nav.pushViewController(FeedViewController(),animated:false)
FeedViewController現(xiàn)在是根視圖控制器。您將繼續(xù)使用ClassicFeedViewController.swift進(jìn)行參考,但是FeedViewController將用于實(shí)現(xiàn)新的IGListKit強(qiáng)制集合視圖。
構(gòu)建并運(yùn)行并確保一個(gè)新的,空的視圖控制器顯示在屏幕上。
添加日記
打開FeedViewController.swift并將以下屬性添加到FeedViewController的頂部:
let loader = JournalEntryLoader()
JournalEntryLoader是一個(gè)將硬編碼的日記帳分錄加載到條目數(shù)組中的類。
將以下內(nèi)容添加到viewDidLoad()的底部:
loader.loadLatest()
loadLatest()是加載最新日記帳分錄的JournalEntryLoader方法。
添加 collectionView
現(xiàn)在是開始向視圖控制器添加一些IGListKit特定控件的時(shí)候了。在你做之前,你需要導(dǎo)入框架。在FeedViewController.swift頂部附近添加一個(gè)新的導(dǎo)入:
導(dǎo)入IGListKit
注意:本教程中的項(xiàng)目使用CocoaPods來管理依賴關(guān)系。 IGListKit是在Objective-C中編寫的,所以如果您手動(dòng)將其添加到項(xiàng)目中,則需要#import到您的橋接頭
將初始化的collectionView常量添加到FeedViewController的頂部:
let collectionView: IGListCollectionView = {
let view = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
view.backgroundColor = UIColor.black
return view
}()
IGListKit使用IGListCollectionView,它是UICollectionView的一個(gè)子類,它優(yōu)化一些功能并阻止其他功能。
這是從零大小的rect開始,因?yàn)橐晥D尚未創(chuàng)建。它正如ClassicFeedViewController一樣使用UICollectionViewFlowLayout。
背景顏色設(shè)置為NASA批準(zhǔn)的黑色。
將以下內(nèi)容添加到viewDidLoad()的底部:
view.addSubview(CollectionView)
這將新的collectionView添加到控制器的視圖。
viewDidLoad()下面添加以下內(nèi)容:
override func viewDidLayoutSubviews(){
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
viewDidLayoutSubviews()被覆蓋,設(shè)置collectionView框架以匹配視圖邊界。
IGListAdapter and data source
使用UICollectionView,需要采用UICollectionViewDataSource的某種數(shù)據(jù)源。 它的工作是返回部分和行數(shù)以及單個(gè)單元格。
在IGListKit中,可以使用所謂的IGListAdapter來控制集合視圖。 我們?nèi)匀恍枰螴GListAdapterDataSource協(xié)議的數(shù)據(jù)源,而不是返回計(jì)數(shù)和單元格,您可以提供數(shù)組和段控制器(稍后再提供)。
對(duì)于初學(xué)者,在FeedViewController.swift中,在FeedViewController的頂部添加以下內(nèi)容:
lazyvaradapter:IGListAdapter= {returnIGListAdapter(updater:IGListAdapterUpdater(), viewController:self, workingRangeSize:0)}()
這將為IGListAdapter創(chuàng)建一個(gè)延遲初始化的變量。 初始化器需要三個(gè)參數(shù):
updater是符合IGListUpdatingDelegate的對(duì)象,它處理行和部分更新。 IGListAdapterUpdater是適合使用的默認(rèn)實(shí)現(xiàn)。
viewController是容納適配器的UIViewController。 此視圖控制器稍后用于導(dǎo)航到其他視圖控制器。
workingRangeSize是工作范圍的大小,它允許您為可見框架之外的部分準(zhǔn)備內(nèi)容。
注意:工作范圍是本教程未涉及的更高級(jí)的主題。 但是,IGListKit的備份還有很多文檔甚至是一個(gè)示例應(yīng)用程序!
將以下內(nèi)容添加到viewDidLoad()的底部:
adapter.collectionView = collectionView
adapter.dataSource =self
這是將collectionView連接到適配器。 它還將self設(shè)置為適配器的dataSource,這會(huì)導(dǎo)致編譯器錯(cuò)誤,因?yàn)槟形床捎肐GListAdapterDataSource協(xié)議。
通過將FeedViewController擴(kuò)展為采用IGListAdapterDataSource來解決此問題。 將以下內(nèi)容添加到文件的底部:
extension FeedViewController: IGListAdapterDataSource {
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
return loader.entries
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
return IGListSectionController()}
func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil }
}
FeedViewController現(xiàn)在遵守IGListAdapterDataSource并實(shí)現(xiàn)其三個(gè)必需的方法:
objects(for:)返回應(yīng)該在集合視圖中顯示的數(shù)據(jù)對(duì)象數(shù)組。 這里提供了loader.entries,因?yàn)樗沼泿し咒洝?/p>
對(duì)于每個(gè)數(shù)據(jù)對(duì)象,listAdapter(_:sectionControllerFor :)必須返回一個(gè)節(jié)控制器的新實(shí)例。 現(xiàn)在,您正在返回一個(gè)簡(jiǎn)單的IGListSectionController來安撫編譯器 - 稍后,您將修改此命令以返回自定義日志部分控制器。
emptyView(for :)返回當(dāng)列表為空時(shí)應(yīng)顯示的視圖。 美國(guó)宇航局處于一個(gè)時(shí)間緊張狀態(tài),所以他們沒有預(yù)算這個(gè)功能。
創(chuàng)建第一個(gè)小組控制器
給定一個(gè)數(shù)據(jù)對(duì)象節(jié)控制器這個(gè)抽象概念,它可以在collection view的一部分中配置和控制單元格。 這個(gè)概念類似于配置視圖的view-model:數(shù)據(jù)對(duì)象是view-model,單元格是視圖。 小組控制器作為兩者之間的粘合劑。
在IGListKit中,您可以為不同類型的數(shù)據(jù)和行為創(chuàng)建一個(gè)新的小組控制器。 JPL工程師已經(jīng)建立了一個(gè)JournalEntry模型,所以你需要?jiǎng)?chuàng)建一個(gè)可以處理它的小組控制器。
右鍵單擊SectionControllers組,然后選擇新建文件...。 創(chuàng)建一個(gè)新的Cocoa Touch類,名為JournalSectionController,繼承IGListSectionController。
Xcode不會(huì)自動(dòng)導(dǎo)入第三方框架,所以在JournalSectionController.swift中添加一行:
import IGListKit
添加一下屬性
varentry: JournalEntry!
let solFormatter = SolFormatter()
JournalEntry是一個(gè)在實(shí)現(xiàn)數(shù)據(jù)源時(shí)將使用的模型類。 SolFormatter類提供了將日期轉(zhuǎn)換為Sol格式的方法。 你很快就需要。
另外在JournalSectionController中,通過添加以下內(nèi)容來覆蓋init():
override init() {
? ? super.init()
? ? inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
}
如果沒有這樣的話,各部分之間的單元格將彼此相鄰。這將添加一個(gè)15點(diǎn)填充到JournalSectionController對(duì)象的底部。
您的小組控制器需要符合IGListSectionType協(xié)議,才能在IGListKit中使用。首先將以下擴(kuò)展名添加到文件的底部:
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) {}
您已經(jīng)實(shí)現(xiàn)了IGListSectionType協(xié)議的四個(gè)必需的方法。
numberOfItems默認(rèn)返回2,分別對(duì)應(yīng)日期和文本。 你可以參考一下ClassicFeedViewController.swift,collectionView(_:numberOfItemsInSection :)中也返回2, 這是一樣的
在doUpdate(到:)中,添加以下內(nèi)容:
entry =objectas? JournalEntry
didUpdate(to :)用于將對(duì)象傳遞給小組控制器。 注意,這種方法將始終在任何cell協(xié)議方法之前被調(diào)用。 在這里,保存?zhèn)魅氲膶?duì)象的數(shù)組
現(xiàn)在有了數(shù)據(jù),開始配置cell。 在cellForItem(at :)中實(shí)現(xiàn):
let cellClass: AnyClass = index == 0 ? JournalEntryDateCell.self : JournalEntryCell.self
let cell = collectionContext!.dequeueReusableCell(of: cellClass, for: self, at: index)
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
接下來,在 sizeForItem(at :) 中實(shí)現(xiàn):
func sizeForItem(at index: Int) -> CGSize {// 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)
}
}
現(xiàn)在你有一個(gè)控制器接收一個(gè)JournalEntry對(duì)象并返回和大小兩個(gè)單元格。 現(xiàn)在是時(shí)候把它們整合在一起了。
返回到FeedViewController.swift中,將以下內(nèi)容替換為listAdapter(_:sectionControllerFor :)的內(nèi)容:
returnJournalSectionController()
現(xiàn)在編譯一下, 應(yīng)該可以看到和初始狀態(tài)一樣的樣子了
后面就是相似的步驟了,添加了兩種消息樣式, 完成后的Demo, 點(diǎn)擊這里
總結(jié)?
IGListKit 的優(yōu)點(diǎn)
1. 不要再次調(diào)用performBatchUpdates(_ :, completion :)或reloadData()
2. 更好的架構(gòu)與可重復(fù)使用的單元和組件
3. 創(chuàng)建具有多種數(shù)據(jù)類型的Collections
4. 解耦差分算法
5. 完全單元測(cè)試
6. 定制您的模型的差異行為
7. 簡(jiǎn)單的UICollectionView是其核心
8. 可擴(kuò)展API
9. 使用Objective-C語言,同時(shí)全面支持Swift