Swift:比 UICollectionView更好用的IGListKit教程

原文: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父能。

image

這個 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小時的時間藻雪。

image

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é)叼丑。

image

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念恍。

image

添加 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
}()
  1. IGListKit 使用了 IGListCollectionView, 這是一個 UICollectionView 的子類叽赊,添加了某些功能并修復了某些缺陷。
  2. 一開始用一個大小為 0 的 frame必搞,因為 view 都還沒創(chuàng)建必指。它也使用了 UICollectionViewFlowLayout ,就像 ClassicFeedViewController 一樣恕洲。
  3. 背景色設為 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ù):

  1. updater 是一個實現(xiàn)了 IGListUpdatingDelegate 協(xié)議的對象, 它負責處理 row 和 section 的刷新晴玖。IGListAdapterUpdater 有一個默認實現(xiàn)读存,剛好給我們用。
  2. viewController 是一個 UIViewController 呕屎,它擁有這個 adapter。 這個 view controller 后面會用于導航到別的 view controllers敬察。
  3. 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 個必須的方法:

  1. objects(for:) 返回一個數(shù)據(jù)對象組成的數(shù)組简软,這些對象將顯示在 collection view。這里返回了loader.entries述暂,因為它包含了日志記錄痹升。
  2. 對于每個數(shù)據(jù)對象,listAdapter(_:sectionControllerFor:) 方法必須返回一個新的 section conroller 實例∑杈拢現(xiàn)在疼蛾,你返回了一個空的 IGListSectionController以解除編譯器的抱怨——等會,你會修改這里艺配,返回一個自定義的日志的 section controller察郁。
  3. 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晾浴。

image

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)時調用。以上代碼解釋如下:

  1. 如果 index 是第一個薄疚,返回 JournalEntryDateCell 單元格碧信,否則返回 JournalEntryCell 單元格。日志數(shù)據(jù)總是先顯示日期街夭,然后才是文本砰碴。
  2. 從緩存中取出一個 cell,dequeue 時需要指定 cell 的類型板丽,一個 section controller 對象衣式,以及 index。
  3. 根據(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 對象汰寓。

運行程序口柳,你將看到一個航空日志的列表!

image

添加消息

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,看看將消息集成后的效果窘哈!

image

火星天氣預報

我們的宇航員需要知道當前天氣以便避開某些東西比如沙塵暴吹榴。JPL 編寫了一個顯示當前天氣的模塊。但是那個信息有點多滚婉,因此他們要求只有在用戶點擊之后才顯示天氣信息图筹。

image

編寫最后一個 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)
    }
  }
}
  1. 在 didUpdate(to:) 方法中驱犹,你保存了傳入的 Weather 對象。
  2. 如果天氣被展開足画,numberOfItems() 返回 5 個 cell雄驹,這樣它會包含天氣數(shù)據(jù)的每個部分。如果不是展開狀態(tài)锌云,只返回一個用于顯示占位內容的 cell荠医。
  3. 第一個 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 標簽:

    1. “Sunrise” - weather.sunrise
    2. “Sunset” - weather.sunset
    3. “High” - “(weather.high) C”
    4. “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ù)。代碼解釋如下:

  1. 將 currentWeather 添加到 items 數(shù)組贝咙。
  2. 讓所有數(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试伙,展開和收起它嘁信!

image

更新操作

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í)行修改動畫。

image

現(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。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末顶伞,一起剝皮案震驚了整個濱河市饵撑,隨后出現(xiàn)的幾起案子剑梳,更是在濱河造成了極大的恐慌,老刑警劉巖滑潘,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垢乙,死亡現(xiàn)場離奇詭異,居然都是意外死亡语卤,警方通過查閱死者的電腦和手機追逮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粹舵,“玉大人钮孵,你說我怎么就攤上這事∑胗ぃ” “怎么了油猫?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柠偶。 經常有香客問我,道長睬关,這世上最難降的妖魔是什么诱担? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮电爹,結果婚禮上蔫仙,老公的妹妹穿的比我還像新娘。我一直安慰自己丐箩,他們只是感情好摇邦,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屎勘,像睡著了一般施籍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上概漱,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天丑慎,我揣著相機與錄音,去河邊找鬼瓤摧。 笑死竿裂,一個胖子當著我的面吹牛,可吹牛的內容都是我干的照弥。 我是一名探鬼主播腻异,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼这揣!你這毒婦竟也來了悔常?” 一聲冷哼從身側響起影斑,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎这嚣,沒想到半個月后鸥昏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡姐帚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年吏垮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罐旗。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡膳汪,死狀恐怖,靈堂內的尸體忽然破棺而出九秀,到底是詐尸還是另有隱情遗嗽,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布鼓蜒,位于F島的核電站痹换,受9級特大地震影響,放射性物質發(fā)生泄漏都弹。R本人自食惡果不足惜娇豫,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畅厢。 院中可真熱鬧冯痢,春花似錦、人聲如沸框杜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咪辱。三九已至振劳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梧乘,已是汗流浹背澎迎。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留选调,地道東北人夹供。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像仁堪,于是被迫代替她去往敵國和親哮洽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內容

  • 1弦聂、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,988評論 3 119
  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫鸟辅、插件氛什、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,121評論 4 61
  • 六種常見的關系以及耦合強度:依賴 < 關聯(lián) < 聚合 < 組合 < 實現(xiàn) <繼承 1、依賴關系(Dependen...
    雙魚子曰1987閱讀 741評論 0 1
  • 感恩物流公司的老板耐心的幫我查貨單匪凉,打了幾次電話也沒有不耐煩枪眉,雖然發(fā)往鄭州的東西第六天了還沒到,因為是食品再层,里面的...
    莀寶貝閱讀 172評論 0 1