IGListKit框架詳細解析(二) —— 基于IGListKit框架的更好的UICollectionViews簡單示例(一)

版本記錄

版本號 時間
V1.0 2019.01.19 星期六

前言

IGListKit這個框架可能很多人沒有聽過,它其實就是一個數(shù)據(jù)驅(qū)動的UICollectionView框架,用于構建快速靈活的列表。它由Instagram開發(fā),接下來這幾篇我們就一起看一下這個框架。感興趣的看上面幾篇。
1. IGListKit框架詳細解析(一) —— 基本概覽(一)

開始

首先看下寫作環(huán)境

Swift 4.2, iOS 12, Xcode 10

每個應用程序都以相同的方式啟動:幾個屏幕,一些按鈕,也許一兩個列表。 但隨著時間的推移和應用程序的增長,功能開始逐漸涌入。在最后期限和產(chǎn)品經(jīng)理的壓力下,您的清潔數(shù)據(jù)源開始崩潰。 過了一會兒,你留下了大量的視圖控制器廢墟來維持。 幸運的是,有一個問題的解決方案!

在使用UICollectionView時,Instagram創(chuàng)建了IGListKit,使功能蠕變和大規(guī)模視圖控制器成為過去。 通過使用IGListKit創(chuàng)建列表,您可以構建具有分離組件,快速更新和支持任何類型數(shù)據(jù)的應用程序。

在本教程中,您將使用IGListKit重構一個基本的UICollectionView,然后擴展應用程序!

您是美國宇航局頂級軟件工程師之一,也是最新載人火星任務的工作人員。 該團隊已經(jīng)構建了Marslink應用程序的第一個版本。

打開已有工程Marslink.xcworkspace,然后構建并運行該應用程序。

到目前為止,該應用程序只顯示了一份宇航員日記條目列表。

您的任務是在工作人員需要時為此應用添加新功能。通過打開ClassicFeedViewController.swift并瀏覽一下,熟悉項目。

如果你曾經(jīng)使用過UICollectionView,你看到的看起來非常標準:

  • ClassicFeedViewController是一個UIViewController子類,它在擴展中實現(xiàn)UICollectionViewDataSource
  • viewDidLoad()創(chuàng)建一個UICollectionView,注冊單元格,設置數(shù)據(jù)源并將其添加到視圖層次結構中。
  • loader.entries數(shù)組提供section的數(shù)量,每個section只有兩個單元格(一個用于日期,一個用于文本)。
  • Date單元格包含日期文本的Sol date和文本Journal單元格。
  • collectionView(_:layout:sizeForItemAt :)返回日期單元格的固定大小,并計算實際條目的文本大小。

一切似乎工作得很好,但項目主管提出了一些緊急的產(chǎn)品更新請求:

一名宇航員剛剛被困在火星上。我們需要您添加天氣模塊和實時聊天。你有48小時。

來自JPL的工程師可以使用其中一些系統(tǒng),但他們需要您的幫助才能將它們添加到應用程序中。

如果將宇航員送回家的壓力不足,NASA的首席設計師只是向您提出要求,即應用程序中每個子系統(tǒng)的更新都必須進行動畫處理,這意味著沒有reloadData()

您應該如何將這些新模塊集成到現(xiàn)有應用程序中并使所有過渡動畫?


Introducing IGListKit

雖然UICollectionView是一個非常強大的工具,但強大的功能帶來了巨大的責任。 保持數(shù)據(jù)源和視圖同步至關重要,但如果斷開連接通常會導致崩潰。

IGListKit是由Instagram團隊構建的數(shù)據(jù)驅(qū)動的UICollectionView框架。 使用此框架,您可以提供要在UICollectionView中顯示的對象數(shù)組。 對于每種類型的對象,適配器adapter都會創(chuàng)建一個稱為節(jié)控制器section controller的東西,它具有創(chuàng)建單元格的所有細節(jié)。

IGListKit會自動對您的對象進行區(qū)分,并在UICollectionView上執(zhí)行動畫批量更新以進行更改。 這樣您就不必自己編寫批量更新,從而避免在此處here的警告中列出的問題。


Adding IGListKit to a UICollectionView

IGListKit完成了識別集合中的更改以及使用動畫更新相應行的所有艱苦工作。 它的結構也可以輕松處理具有不同數(shù)據(jù)和UI的多個部分。 考慮到這一點,它是新一批處理要求的完美解決方案 - 因此是時候開始實施它了!

Marslink.xcworkspace仍然打開的情況下,右鍵單擊ViewControllers組并選擇New File。 添加一個新的Cocoa Touch Class,它將UIViewController的子類名為FeedViewController,并確保將語言設置為Swift

打開AppDelegate.swift并找到application(_:didFinishLaunchingWithOptions:)。 找到將ClassicFeedViewController()推送到導航控制器的行,并將其替換為:

nav.pushViewController(FeedViewController(), animated: false)

FeedViewController現(xiàn)在是根視圖控制器。 您將保留ClassicFeedViewController.swift作為參考,但FeedViewController是您將實現(xiàn)新的IGListKit驅(qū)動的collection view的地方。

構建并運行并確保在屏幕上顯示一個新的空視圖控制器。

1. Adding the Journal Loader

打開FeedViewController.swift并將以下屬性添加到FeedViewController的頂部:

let loader = JournalEntryLoader()

JournalEntryLoader是一個將硬編碼日記條目加載到entries數(shù)組中的類。

將以下內(nèi)容添加到viewDidLoad()的底部:

loader.loadLatest()

loadLatest()是一個JournalEntryLoader方法,用于加載最新的日記帳分錄。

2. Adding the Collection View

是時候開始向視圖控制器添加一些IGListKit特定的控件了。 在此之前,您需要導入框架。 在FeedViewController.swift的頂部附近,添加一個新的import

import IGListKit

注意:本教程中的項目使用CocoaPods來管理依賴項。 IGListKit是用Objective-C編寫的,因此如果手動將其添加到項目中,則需要將#import插入到橋接頭 bridging header中。

將初始化的collectionView常量添加到FeedViewController的頂部:

// 1
let collectionView: UICollectionView = {
  // 2
  let view = UICollectionView(
    frame: .zero, 
    collectionViewLayout: UICollectionViewFlowLayout())
  // 3
  view.backgroundColor = .black
  return view
}()

這是代碼的作用:

  • 1) IGListKit使用常規(guī)的UICollectionView并在其上添加自己的功能,稍后您將看到。
  • 2) 從零大小的rect開始,因為尚未創(chuàng)建視圖。 它像ClassicFeedViewController一樣使用UICollectionViewFlowLayout
  • 3) 將背景顏色設置為NASA認可的黑色。

將以下內(nèi)容添加到viewDidLoad()的底部:

view.addSubview(collectionView)

這會將新的collectionView添加到控制器的視圖中。

viewDidLoad()下面,添加以下內(nèi)容:

override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()
  collectionView.frame = view.bounds
}

這將重寫viewDidLayoutSubviews(),將collectionViewframe設置為與視圖bounds相同。

3. ListAdapter and Data Source

使用UICollectionView,您需要某種采用UICollectionViewDataSource的數(shù)據(jù)源。 它的工作是返回section and row數(shù)以及單個單元格。

IGListKit中,您使用ListAdapter來控制集合視圖。 您仍然需要一個符合協(xié)議ListAdapterDataSource的數(shù)據(jù)源,但不是返回計數(shù)和單元格,而是提供數(shù)組和節(jié)控制器(section controllers)(稍后將詳細介紹)。

對于初學者,在FeedViewController.swift中,在FeedViewController的頂部添加以下內(nèi)容:

lazy var adapter: ListAdapter = {
  return ListAdapter(
  updater: ListAdapterUpdater(),
  viewController: self, 
  workingRangeSize: 0)
}()

這將為ListAdapter創(chuàng)建一個初始化變量。 初始化程序需要三個參數(shù):

  • 1) updater是符合ListUpdatingDelegate的對象,它處理row and section更新。 ListAdapterUpdater是一個適合您使用的默認實現(xiàn)。
  • 2) viewController是一個容納適配器的UIViewControllerIGListKit稍后使用此視圖控制器導航到其他視圖控制器。
  • 3) workingRangeSizeworking range的大小,允許您為可見框外部的部分準備內(nèi)容。

注意:工作范圍Working ranges是本教程未涵蓋的更高級主題。 然而,IGListKit repo中有大量文檔甚至是一個示例應用程序!

將以下內(nèi)容添加到viewDidLoad()的底部:

adapter.collectionView = collectionView
adapter.dataSource = self

這將collectionView連接到適配器adapter。 它還將self設置為適配器的dataSource - 導致編譯器錯誤,因為您尚未符合ListAdapterDataSource

通過擴展FeedViewController以采用ListAdapterDataSource來解決此問題。 將以下內(nèi)容添加到文件的底部:

// MARK: - ListAdapterDataSource
extension FeedViewController: ListAdapterDataSource {
  // 1
  func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
    return loader.entries
  }
  
  // 2
  func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) 
  -> ListSectionController {
    return ListSectionController()
  }
  
  // 3
  func emptyView(for listAdapter: ListAdapter) -> UIView? {
    return nil
  }
}

注意:IGListKit大量使用所需的協(xié)議方法。 即使你可能最終得到空方法,或者返回nil的方法,你也不必擔心默默地丟失方法或者對抗動態(tài)運行時。 它使得使用IGListKit非常困難。

FeedViewController現(xiàn)在遵循ListAdapterDataSource并實現(xiàn)其三個必需的方法:

  • 1) objects(for :)返回應顯示在集合視圖中的數(shù)據(jù)對象數(shù)組。 您在此處提供loader.entries,因為它包含日記帳分錄。
  • 2) 對于每個數(shù)據(jù)對象,listAdapter(_:sectionControllerFor :)必須返回一個節(jié)控制器section controller的新實例。 現(xiàn)在你要返回一個普通的ListSectionController來讓編譯器不報錯。 稍后,您將修改此項以返回自定義日記記錄section controller
  • 3) emptyView(for :)返回一個視圖,當列表為空時顯示。 美國宇航局有點緊張,所以他們沒有預算這個功能。

4. Creating Your First Section Controller

section controller是一種抽象,在給定數(shù)據(jù)對象的情況下,它在集合視圖的一section中配置和控制單元。 此概念類似于用于配置視圖的視圖模型view-model:數(shù)據(jù)對象是視圖模型,單元格是視圖。 section controller充當兩者之間的粘合劑。

IGListKit中,您可以為不同類型的數(shù)據(jù)和行為創(chuàng)建新的section controllerJPL工程師已經(jīng)構建了一個JournalEntry模型,因此您需要創(chuàng)建一個可以處理它的節(jié)控制器。

右鍵單擊SectionControllers組并選擇New File。 創(chuàng)建一個名為JournalSectionController的新Cocoa Touch類,它是ListSectionController的子類。

Xcode不會自動導入第三方框架,因此在JournalSectionController.swift中,在頂部添加一行:

import IGListKit

將以下屬性添加到JournalSectionController的頂部:

var entry: JournalEntry!
let solFormatter = SolFormatter()

JournalEntry是您在實現(xiàn)數(shù)據(jù)源時將使用的模型類。 SolFormatter類提供將日期轉換為Sol格式的方法。 你很快就會需要兩個。

同樣在JournalSectionController中,通過添加以下內(nèi)容來重寫init()

override init() {
  super.init()
  inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
}

如果沒有這個,sections之間的單元格將彼此相鄰。 這會在JournalSectionController對象的底部添加15點填充。

您的節(jié)控制器需要重寫ListSectionController中的四個方法,以提供適配器使用的實際數(shù)據(jù)。

將以下擴展添加到文件的底部:

// MARK: - Data Provider
extension JournalSectionController {
  override func numberOfItems() -> Int {
    return 2
  }
  
  override func sizeForItem(at index: Int) -> CGSize {
    return .zero
  }
  
  override func cellForItem(at index: Int) -> UICollectionViewCell {
    return UICollectionViewCell()
  }
  
  override func didUpdate(to object: Any) {
  }  
}

除了numberOfItems()之外,所有方法都是存根實現(xiàn),它只是為日期和文本對返回2。 如果您回顧ClassicFeedViewController.swift,您會注意到在collectionView(_:numberOfItemsInSection :)中每個部分也返回2個項目。 這基本上是一回事!

didUpdate(to :)中,添加以下內(nèi)容:

entry = object as? JournalEntry

IGListKit調(diào)用didUpdate(to :)將對象傳遞給節(jié)控制器(section controller.)。 請注意,在任何單元協(xié)議方法之前始終調(diào)用此方法。 在這里,您將傳入的對象保存在entry中。

注意:對象在段控制器的生命周期內(nèi)可以多次更改。 只有當您開始解鎖IGListKit的更高級功能(例如custom model diffing)時才會發(fā)生這種情況。 您不必擔心本教程中的差異。

現(xiàn)在您有了一些數(shù)據(jù),您可以開始配置您的單元格。 用以下代碼替換cellForItem(at :)的占位符實現(xiàn):

// 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

IGListKit在需要section中給定索引的單元格時調(diào)用cellForItem(at :)。 以下是代碼的工作原理:

  • 1) 如果索引是第一個,請使用JournalEntryDateCell單元格,否則使用JournalEntryCell單元格。 Journal entries始終顯示日期后跟文本。
  • 2) 使用單元類,section controller(self)和索引將單元從重用池中出列。
  • 3) 根據(jù)單元格類型,使用您之前在didUpdate(to object:)中設置的JournalEntry進行配置。

接下來,使用以下內(nèi)容替換sizeForItem(at :)的占位符實現(xiàn):

// 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)
}

這段代碼的工作原理:

  • 1) collectionContext是一個weak變量,必須是nullable。雖然它永遠不應該是nil,但最好采取預防措施,而Swift guard就是這么簡單。
  • 2) ListCollectionContext是一個上下文對象,其中包含有關使用節(jié)控制器的適配器,集合視圖和視圖控制器的信息。在這里你可以得到容器的寬度。
  • 3) 如果是第一個索引(日期單元格),則返回與容器一樣寬的大小和30個高點。否則,使用單元格幫助程序方法計算單元格的動態(tài)文本大小。

如果您之前使用過UICollectionView,這種將不同類型的單元格出列,配置和返回大小的模式應該都會讓您感到熟悉。同樣,您可以參考ClassicFeedViewController并看到很多此代碼幾乎完全相同。

現(xiàn)在,您有一個section controller,它接收一個JournalEntry對象并返回并調(diào)整兩個單元格的大小。是時候?qū)⑺鼈冋显谝黄鹆恕?/p>

回到FeedViewController.swift,用以下內(nèi)容替換listAdapter(_:sectionControllerFor :)的內(nèi)容:

return JournalSectionController()

只要IGListKit調(diào)用此方法,它就會返回新的journal section controller

構建并運行應用程序。 您應該看到日記帳分錄列表:

后記

本篇主要簡單介紹了基于IGListKit框架的更好的UICollectionViews簡單示例,感興趣的給個贊或者關注~~~

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

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